HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
style_sheet_parser.hpp
1// Copyright Take Vos 2023.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "../utility/module.hpp"
8#include "../parser/module.hpp"
9#include "theme_mode.hpp"
10#include "theme_length.hpp"
11#include "style_sheet.hpp"
12#include <string>
13#include <vector>
14
15namespace hi { inline namespace v1 {
16namespace detail {
17
19public:
20 std::filesystem::path path;
21
22 [[nodiscard]] constexpr bool set_macro(std::string const& name, std::vector<style_sheet_declaration> declarations) noexcept
23 {
24 auto it = std::lower_bound(_macros.begin(), _macros.end(), name, [](hilet& item, hilet& value) {
25 return item.first < value;
26 });
27 if (it != _macros.end() and it->first == name) {
28 return false;
29 } else {
30 _macros.emplace(it, name, std::move(declarations));
31 return true;
32 }
33 }
34
35 constexpr std::optional<std::vector<style_sheet_declaration>> get_macro(std::string const& name) const noexcept
36 {
37 auto it = std::lower_bound(_macros.begin(), _macros.end(), name, [](hilet& item, hilet& value) {
38 return item.first < value;
39 });
40 if (it != _macros.end() and it->first == name) {
41 return it->second;
42 } else {
43 return std::nullopt;
44 }
45 }
46
47 [[nodiscard]] constexpr bool set_let(std::string const& name, style_sheet_value value) noexcept
48 {
49 auto it = std::lower_bound(_lets.begin(), _lets.end(), name, [](hilet& item, hilet& value) {
50 return item.first < value;
51 });
52 if (it != _lets.end() and it->first == name) {
53 return false;
54 } else {
55 _lets.emplace(it, name, value);
56 return true;
57 }
58 }
59
60 constexpr std::optional<style_sheet_value> get_let(std::string const& name) const noexcept
61 {
62 auto it = std::lower_bound(_lets.begin(), _lets.end(), name, [](hilet& item, hilet& value) {
63 return item.first < value;
64 });
65 if (it != _lets.end() and it->first == name) {
66 return it->second;
67 } else {
68 return std::nullopt;
69 }
70 }
71
72 [[nodiscard]] constexpr bool set_color(std::string const& name, hi::color color) noexcept
73 {
74 auto it = std::lower_bound(_colors.begin(), _colors.end(), name, [](hilet& item, hilet& value) {
75 return item.first < value;
76 });
77 if (it != _colors.end() and it->first == name) {
78 return false;
79 } else {
80 _colors.emplace(it, name, color);
81 return true;
82 }
83 }
84
85 constexpr std::optional<color> get_color(std::string const& name) const noexcept
86 {
87 auto it = std::lower_bound(_colors.begin(), _colors.end(), name, [](hilet& item, hilet& value) {
88 return item.first < value;
89 });
90 if (it != _colors.end() and it->first == name) {
91 return it->second;
92 } else {
93 return std::nullopt;
94 }
95 }
96
97 constexpr std::vector<std::pair<std::string, color>> move_colors() noexcept
98 {
99 return std::move(_colors);
100 }
101
102private:
106};
107
108template<typename It, std::sentinel_for<It> ItEnd>
109[[nodiscard]] constexpr std::optional<hi::language_tag>
110parse_style_sheet_theme_state_lang(It& it, ItEnd last, style_sheet_parser_context& context)
111{
112 if (*it == token::id and *it == "lang") {
113 ++it;
114
115 if (it == last or *it != '(') {
116 throw parse_error(std::format("{} Missing '(' after ':lang'.", token_location(it, last, context.path)));
117 }
118 ++it;
119
120 auto language_tag_string = std::string{};
121 while (it != last and *it != ')') {
122 if (*it == '*') {
123 language_tag_string += '*';
124 ++it;
125 } else if (*it == '-') {
126 language_tag_string += '-';
127 ++it;
128 } else if (*it == token::id) {
129 language_tag_string += static_cast<std::string>(*it);
130 ++it;
131 } else {
132 throw parse_error(std::format(
133 "{} Unexpected token while parsing argument of ':lang()'.", token_location(it, last, context.path)));
134 }
135 }
136
137 auto r = [&] {
138 try {
139 return language_tag{language_tag_string};
140 } catch (std::exception const& e) {
141 throw parse_error(std::format(
142 "{} Invalid language-tag '{}' while parsing argument of ':lang()'. {}",
143 token_location(it, last, context.path),
144 language_tag_string, e.what()));
145 }
146 }();
147
148 if (it == last or *it != ')') {
149 throw parse_error(std::format("{} Missing ')' at end of ':lang'.", token_location(it, last, context.path)));
150 }
151 ++it;
152
153 return r;
154 } else {
155 return std::nullopt;
156 }
157}
158
159template<typename It, std::sentinel_for<It> ItEnd>
160[[nodiscard]] constexpr std::optional<hi::text_phrasing_mask>
161parse_style_sheet_theme_state_phrasing(It& it, ItEnd last, style_sheet_parser_context& context)
162{
163 if (*it == token::id and *it == "phrasing") {
164 ++it;
165
166 if (it == last or *it != '(') {
167 throw parse_error(std::format("{} Missing '(' after ':phrasing'.", token_location(it, last, context.path)));
168 }
169 ++it;
170
171 if (it == last or *it != token::id) {
172 throw parse_error(std::format("{} Missing integer after ':phrasing('.", token_location(it, last, context.path)));
173 }
174
175 hilet phrasing_mask = [&] {
176 try {
177 return to_text_phrasing_mask(static_cast<std::string>(*it));
178 } catch (std::exception const& e) {
179 throw parse_error(std::format(
180 "{} Could not convert argument '{}' of ':phrasing()' to integer. {}",
181 token_location(it, last, context.path),
182 static_cast<std::string>(*it),
183 e.what()));
184 }
185 }();
186 ++it;
187
188 if (it == last or *it != ')') {
189 throw parse_error(std::format("{} Missing ')' at end of ':phrasing'.", token_location(it, last, context.path)));
190 }
191 ++it;
192
193 return phrasing_mask;
194 } else {
195 return std::nullopt;
196 }
197}
198
199template<typename It, std::sentinel_for<It> ItEnd>
200[[nodiscard]] constexpr std::optional<std::pair<theme_state, theme_state_mask>>
201parse_style_sheet_theme_state(It& it, ItEnd last, style_sheet_parser_context& context)
202{
203 if (*it == token::id and *it == "disabled") {
204 ++it;
205 return std::pair{theme_state::disabled, theme_state_mask::mouse};
206 } else if (*it == token::id and *it == "enabled") {
207 ++it;
208 return std::pair{theme_state::enabled, theme_state_mask::mouse};
209 } else if (*it == token::id and *it == "hover") {
210 ++it;
211 return std::pair{theme_state::hover, theme_state_mask::mouse};
212 } else if (*it == token::id and *it == "active") {
213 ++it;
214 return std::pair{theme_state::active, theme_state_mask::mouse};
215 } else if (*it == token::id and *it == "no-focus") {
216 ++it;
217 return std::pair{theme_state::no_focus, theme_state_mask::focus};
218 } else if (*it == token::id and *it == "focus") {
219 ++it;
220 return std::pair{theme_state::focus, theme_state_mask::focus};
221 } else if (*it == token::id and *it == "off") {
222 ++it;
223 return std::pair{theme_state::off, theme_state_mask::value};
224 } else if (*it == token::id and *it == "on") {
225 ++it;
226 return std::pair{theme_state::on, theme_state_mask::value};
227 } else if (*it == token::id and *it == "layer") {
228 ++it;
229
230 if (it == last or *it != '(') {
231 throw parse_error(std::format("{} Missing '(' after ':layer'.", token_location(it, last, context.path)));
232 }
233 ++it;
234
235 if (it == last or *it != token::integer) {
236 throw parse_error(std::format("{} Missing integer after ':layer('.", token_location(it, last, context.path)));
237 }
238
239 hilet layer_nr = [&] {
240 try {
241 return static_cast<uint8_t>(*it);
242 } catch (std::exception const& e) {
243 throw parse_error(std::format(
244 "{} Could not convert argument of ':layer()' to integer. {}",
245 token_location(it, last, context.path),
246 e.what()));
247 }
248 }();
249 ++it;
250
251 hilet layer_state = [&] {
252 switch (layer_nr) {
253 case 0:
254 return theme_state::layer_0;
255 case 1:
256 return theme_state::layer_1;
257 case 2:
258 return theme_state::layer_2;
259 case 3:
260 return theme_state::layer_3;
261 default:
262 throw parse_error(std::format(
263 "{} Expect ':layer()' value of 0, 1, 2 or 3, got {}.", token_location(it, last, context.path), layer_nr));
264 }
265 }();
266
267 if (it == last or *it != ')') {
268 throw parse_error(std::format("{} Missing ')' at end of ':layer'.", token_location(it, last, context.path)));
269 }
270 ++it;
271
272 return std::pair{layer_state, theme_state_mask::layers};
273
274 } else {
275 return std::nullopt;
276 }
277}
278
279template<typename It, std::sentinel_for<It> ItEnd>
280[[nodiscard]] constexpr std::optional<style_sheet_pattern>
281parse_style_sheet_pattern(It& it, ItEnd last, style_sheet_parser_context& context)
282{
283 // pattern := ( id | '*' ) ( '>'? ( id | '*' ) )* ( ':' id )*
284 auto r = style_sheet_pattern{};
285
286 if (it != last and *it == '*') {
287 r.path.push_back("*");
288 ++it;
289 } else if (it != last and *it == token::id) {
290 r.path.push_back(static_cast<std::string>(*it));
291 ++it;
292 } else {
293 return std::nullopt;
294 }
295
296 auto is_child = false;
297 while (it != last and *it != ',' and *it != '{' and *it != ':') {
298 if (*it == '>') {
299 is_child = true;
300 ++it;
301
302 } else if (*it == '*') {
303 r.is_child.push_back(is_child);
304 r.path.push_back("*");
305 is_child = false;
306 ++it;
307
308 } else if (*it == token::id) {
309 r.is_child.push_back(is_child);
310 r.path.push_back(static_cast<std::string>(*it));
311 is_child = false;
312 ++it;
313
314 } else {
315 throw parse_error(std::format(
316 "{} Expecting element, '*', '>', ',' or '{{' while parsing selector.", token_location(it, last, context.path)));
317 }
318 }
319
320 return r;
321}
322
323template<typename It, std::sentinel_for<It> ItEnd>
324[[nodiscard]] constexpr std::optional<style_sheet_selector>
325parse_style_sheet_selector(It& it, ItEnd last, style_sheet_parser_context& context)
326{
327 // selector := pattern (',' pattern)*
328 auto r = style_sheet_selector{};
329
330 if (auto pattern = parse_style_sheet_pattern(it, last, context)) {
331 r.push_back(*pattern);
332 } else {
333 return std::nullopt;
334 }
335
336 while (it != last and *it == ',') {
337 ++it;
338
339 if (auto pattern = parse_style_sheet_pattern(it, last, context)) {
340 r.push_back(*pattern);
341 } else {
342 throw parse_error(std::format("{} Missing pattern after ',' in selector.", token_location(it, last, context.path)));
343 }
344 }
345
346 return r;
347}
348
349template<typename It, std::sentinel_for<It> ItEnd>
350[[nodiscard]] constexpr std::optional<float>
351parse_style_sheet_color_component(It& it, ItEnd last, style_sheet_parser_context& context)
352{
353 auto r = 0.0f;
354
355 if (it.size() >= 2 and (it[0] == token::integer or it[0] == token::real) and it[1] == '%') {
356 r = sRGB_gamma_to_linear(static_cast<float>(it[0]) * 0.01f);
357 it += 2;
358 } else if (it != last and *it == token::real) {
359 r = static_cast<float>(*it);
360 ++it;
361 } else if (it.size() >= 2 and it[0] == '-' and it[1] == token::real) {
362 r = -static_cast<float>(it[1]);
363 it += 2;
364 } else if (it != last and *it == token::integer) {
365 r = sRGB_gamma_to_linear(static_cast<float>(*it) / 255.0f);
366 ++it;
367 } else {
368 return std::nullopt;
369 }
370
371 return r;
372}
373
374template<typename It, std::sentinel_for<It> ItEnd>
375[[nodiscard]] constexpr std::optional<float>
376parse_style_sheet_alpha_component(It& it, ItEnd last, style_sheet_parser_context& context)
377{
378 auto r = 0.0f;
379
380 if (it.size() >= 2 and (it[0] == token::integer or it[0] == token::real) and it[1] == '%') {
381 r = static_cast<float>(it[0]) * 0.01f;
382 it += 2;
383 } else if (it != last and *it == token::real) {
384 r = static_cast<float>(*it);
385 ++it;
386 } else {
387 return std::nullopt;
388 }
389
390 return r;
391}
392
393template<typename It, std::sentinel_for<It> ItEnd>
394[[nodiscard]] constexpr std::optional<color> parse_style_sheet_color(It& it, ItEnd last, style_sheet_parser_context& context)
395{
396 if (it != last and *it == token::color) {
397 try {
398 auto r = static_cast<color>(*it);
399 ++it;
400 return r;
401
402 } catch (std::exception const& e) {
403 throw parse_error(std::format(
404 "{} Invalid color literal '{}': {}",
405 token_location(it, last, context.path),
406 static_cast<std::string>(*it),
407 e.what()));
408 }
409
410 } else if (it != last and *it == token::id and *it == "rgb") {
411 // rgb-color := "rgb" '(' color-component ','? color-component ','? color-component ( [,/]? alpha-component )? ')'
412 // color-component := integer | float | number '%'
413 // alpha-component := float | number '%'
414 ++it;
415
416 if (it == last or *it != '(') {
417 throw parse_error(
418 std::format("{} Expect '(' after \"color-layers\" keyword.", token_location(it, last, context.path)));
419 }
420 ++it;
421
422 auto r = color{};
423 r.a() = 1.0;
424
425 if (auto component = parse_style_sheet_color_component(it, last, context)) {
426 r.r() = *component;
427 } else {
428 throw parse_error(std::format("{} Expect a red-color-component after '('.", token_location(it, last, context.path)));
429 }
430
431 if (it != last and *it == ',') {
432 ++it;
433 }
434
435 if (auto component = parse_style_sheet_color_component(it, last, context)) {
436 r.g() = *component;
437 } else {
438 throw parse_error(std::format(
439 "{} Expect a green-color-component after red-color-component.", token_location(it, last, context.path)));
440 }
441
442 if (it != last and *it == ',') {
443 ++it;
444 }
445
446 if (auto component = parse_style_sheet_color_component(it, last, context)) {
447 r.b() = *component;
448 } else {
449 throw parse_error(std::format(
450 "{} Expect a blue-color-component after red-color-component.", token_location(it, last, context.path)));
451 }
452
453 if (it != last and (*it == ',' or *it == '/')) {
454 ++it;
455 }
456
457 // Alpha is optional.
458 if (auto component = parse_style_sheet_alpha_component(it, last, context)) {
459 r.a() = *component;
460 }
461
462 if (it == last or *it != ')') {
463 throw parse_error(std::format("{} Expect ')' after colors-components.", token_location(it, last, context.path)));
464 }
465 ++it;
466 return r;
467
468 } else if (it != last and *it == token::id) {
469 // A color name is looked up from @color declarations
470 hilet name = static_cast<std::string>(*it);
471 ++it;
472
473 if (auto color = context.get_color(name)) {
474 return *color;
475
476 } else {
477 throw parse_error(
478 std::format("{} Color name \"{}\" was not declared with @color.", token_location(it, last, context.path), name));
479 }
480
481 } else {
482 return std::nullopt;
483 }
484}
485
486template<typename It, std::sentinel_for<It> ItEnd>
487[[nodiscard]] constexpr std::vector<color> parse_style_sheet_colors(It& it, ItEnd last, style_sheet_parser_context& context)
488{
489 auto r = std::vector<color>{};
490
491 if (auto color = parse_style_sheet_color(it, last, context)) {
492 r.push_back(*color);
493 } else {
494 return r;
495 }
496
497 if (it != last and *it == ',') {
498 ++it;
499 }
500
501 while (it != last and *it != ';' and *it != '!') {
502 if (auto color = parse_style_sheet_color(it, last, context)) {
503 r.push_back(*color);
504 } else {
505 throw parse_error(std::format("{} Expect a sequence of colors.", token_location(it, last, context.path)));
506 }
507
508 if (it != last and *it == ',') {
509 ++it;
510 }
511 }
512
513 return r;
514}
515
516template<typename It, std::sentinel_for<It> ItEnd>
517[[nodiscard]] constexpr std::optional<theme_length>
518parse_style_sheet_length(It& it, ItEnd last, style_sheet_parser_context& context)
519{
520 if (it.size() >= 2 and (it[0] == token::integer or it[0] == token::real) and it[1] == token::id) {
521 hilet r = [&]() -> theme_length {
522 if (it[1] == "dp") {
523 return dips{static_cast<double>(it[0])};
524 } else if (it[1] == "pt") {
525 return dips{points{static_cast<double>(it[0])}};
526 } else if (it[1] == "mm") {
527 return dips{millimeters{static_cast<double>(it[0])}};
528 } else if (it[1] == "cm") {
529 return dips{centimeters{static_cast<double>(it[0])}};
530 } else if (it[1] == "dm") {
531 return dips{decimeters{static_cast<double>(it[0])}};
532 } else if (it[1] == "m") {
533 return dips{meters{static_cast<double>(it[0])}};
534 } else if (it[1] == "in") {
535 return dips{inches{static_cast<double>(it[0])}};
536 } else if (it[1] == "px") {
537 return pixels(static_cast<double>(it[0]));
538 } else if (it[1] == "em") {
539 return em_quads(static_cast<double>(it[0]));
540 } else {
541 throw parse_error(std::format(
542 "{} Expected either \"pt\", \"cm\", \"in\", \"em\" or \"px\" after number",
543 token_location(it, last, context.path)));
544 }
545 }();
546
547 it += 2;
548 return r;
549
550 } else if (it != last and (*it == token::integer or *it == token::real)) {
551 // Implicitly a number without suffix is in `dp`.
552 hilet r = theme_length{dips{static_cast<float>(*it)}};
553 ++it;
554 return r;
555
556 } else {
557 return std::nullopt;
558 }
559}
560
561template<typename It, std::sentinel_for<It> ItEnd>
562[[nodiscard]] constexpr std::vector<theme_length>
563parse_style_sheet_lengths(It& it, ItEnd last, style_sheet_parser_context& context)
564{
565 auto r = std::vector<theme_length>{};
566
567 if (auto length = parse_style_sheet_length(it, last, context)) {
568 r.push_back(*length);
569 } else {
570 return r;
571 }
572
573 if (it != last and *it == ',') {
574 ++it;
575 }
576
577 while (it != last and *it != ';' and *it != '!') {
578 if (auto length = parse_style_sheet_length(it, last, context)) {
579 r.push_back(*length);
580 } else {
581 throw parse_error(std::format("{} Expect a sequence of lengths.", token_location(it, last, context.path)));
582 }
583
584 if (it != last and *it == ',') {
585 ++it;
586 }
587 }
588
589 return r;
590}
591
592template<typename It, std::sentinel_for<It> ItEnd>
593[[nodiscard]] constexpr std::optional<style_sheet_value>
594parse_style_sheet_let_expansion(It& it, ItEnd last, style_sheet_parser_context& context)
595{
596 if (it.size() < 2 or it[0] != '@' or it[1] != token::id) {
597 return std::nullopt;
598 }
599
600 hilet name = static_cast<std::string>(it[1]);
601 it += 2;
602
603 if (auto value = context.get_let(name)) {
604 return {value};
605 } else {
606 throw parse_error(std::format("{} Trying to expand undeclared @let {}.", token_location(it, last, context.path), name));
607 }
608}
609
610template<typename It, std::sentinel_for<It> ItEnd>
611[[nodiscard]] constexpr std::optional<style_sheet_value>
612parse_style_sheet_value(It& it, ItEnd last, style_sheet_parser_context& context)
613{
614 if (auto value = parse_style_sheet_let_expansion(it, last, context)) {
615 return value;
616
617 } else if (auto color = parse_style_sheet_color(it, last, context)) {
618 return style_sheet_value{*color};
619
620 } else if (auto length = parse_style_sheet_length(it, last, context)) {
621 return style_sheet_value{*length};
622
623 } else {
624 return std::nullopt;
625 }
626}
627
628template<typename It, std::sentinel_for<It> ItEnd>
629[[nodiscard]] constexpr std::vector<style_sheet_declaration>
630parse_style_sheet_font_family_declaration(It& it, ItEnd last, style_sheet_parser_context& context)
631{
632 auto family_id = font_family_id{};
633 while (it != last and *it != ';' and *it != '!') {
634 if (*it == ',') {
635 // Ignore optional ','
636 ++it;
637
638 } else if (family_id) {
639 // If family_id was already found, just consume tokens until ';' or '!' is found.
640 ++it;
641
642 } else if (*it == token::id and *it == "serif") {
643 if (family_id = find_font_family("Times New Roman")) {
644 } else if (family_id = find_font_family("Big Caslon")) {
645 } else if (family_id = find_font_family("Bodoni MT")) {
646 } else if (family_id = find_font_family("Book Antique")) {
647 } else if (family_id = find_font_family("Bookman")) {
648 } else if (family_id = find_font_family("New Century Schoolbook")) {
649 } else if (family_id = find_font_family("Calisto MT")) {
650 } else if (family_id = find_font_family("Cambria")) {
651 } else if (family_id = find_font_family("Didot")) {
652 } else if (family_id = find_font_family("Garamond")) {
653 } else if (family_id = find_font_family("Georgia")) {
654 } else if (family_id = find_font_family("Goudy Old Style")) {
655 } else if (family_id = find_font_family("Hoeflet Text")) {
656 } else if (family_id = find_font_family("Lucida Bright")) {
657 } else if (family_id = find_font_family("Palatino")) {
658 } else if (family_id = find_font_family("Perpetua")) {
659 } else if (family_id = find_font_family("Rockwell")) {
660 } else if (family_id = find_font_family("Baskerville")) {
661 } else {
662 throw parse_error(
663 std::format("{} Could not find any serif fallback font.", token_location(it, last, context.path)));
664 }
665 ++it;
666
667 } else if (*it == token::id and *it == "sans-serif") {
668 if (family_id = find_font_family("Arial")) {
669 } else if (family_id = find_font_family("Helvetica")) {
670 } else if (family_id = find_font_family("Verdana")) {
671 } else if (family_id = find_font_family("Calibri")) {
672 } else if (family_id = find_font_family("Noto")) {
673 } else if (family_id = find_font_family("Lucida Sans")) {
674 } else if (family_id = find_font_family("Gill Sans")) {
675 } else if (family_id = find_font_family("Century Gothic")) {
676 } else if (family_id = find_font_family("Candara")) {
677 } else if (family_id = find_font_family("Futara")) {
678 } else if (family_id = find_font_family("Franklin Gothic Medium")) {
679 } else if (family_id = find_font_family("Trebuchet MS")) {
680 } else if (family_id = find_font_family("Geneva")) {
681 } else if (family_id = find_font_family("Segoe UI")) {
682 } else if (family_id = find_font_family("Optima")) {
683 } else if (family_id = find_font_family("Avanta Garde")) {
684 } else {
685 throw parse_error(
686 std::format("{} Could not find any sans-serif fallback font.", token_location(it, last, context.path)));
687 }
688 ++it;
689
690 } else if (*it == token::id and *it == "monospace") {
691 if (family_id = find_font_family("Consolas")) {
692 } else if (family_id = find_font_family("Courier")) {
693 } else if (family_id = find_font_family("Courier New")) {
694 } else if (family_id = find_font_family("Lucida Console")) {
695 } else if (family_id = find_font_family("Lucidatypewriter")) {
696 } else if (family_id = find_font_family("Lucida Sans Typewriter")) {
697 } else if (family_id = find_font_family("Monaco")) {
698 } else if (family_id = find_font_family("Andale Mono")) {
699 } else {
700 throw parse_error(
701 std::format("{} Could not find any monospace fallback font.", token_location(it, last, context.path)));
702 }
703 ++it;
704
705 } else if (*it == token::id and *it == "cursive") {
706 if (family_id = find_font_family("Comic Sans")) {
707 } else if (family_id = find_font_family("Comic Sans MS")) {
708 } else if (family_id = find_font_family("Apple Chancery")) {
709 } else if (family_id = find_font_family("Zapf Chancery")) {
710 } else if (family_id = find_font_family("Bradly Hand")) {
711 } else if (family_id = find_font_family("Brush Script MT")) {
712 } else if (family_id = find_font_family("Brush Script Std")) {
713 } else if (family_id = find_font_family("Snell Roundhan")) {
714 } else if (family_id = find_font_family("URW Chancery")) {
715 } else if (family_id = find_font_family("Coronet script")) {
716 } else if (family_id = find_font_family("Florence")) {
717 } else if (family_id = find_font_family("Parkavenue")) {
718 } else {
719 throw parse_error(
720 std::format("{} Could not find any monospace fallback font.", token_location(it, last, context.path)));
721 }
722 ++it;
723
724 } else if (*it == token::id and *it == "fantasy") {
725 if (family_id = find_font_family("Impact")) {
726 } else if (family_id = find_font_family("Brushstroke")) {
727 } else if (family_id = find_font_family("Luminari")) {
728 } else if (family_id = find_font_family("Chalkduster")) {
729 } else if (family_id = find_font_family("Jazz LET")) {
730 } else if (family_id = find_font_family("Blippo")) {
731 } else if (family_id = find_font_family("Stencil Std")) {
732 } else if (family_id = find_font_family("Market Felt")) {
733 } else if (family_id = find_font_family("Trattatello")) {
734 } else if (family_id = find_font_family("Arnoldboecklin")) {
735 } else if (family_id = find_font_family("Oldtown")) {
736 } else if (family_id = find_font_family("Copperplate")) {
737 } else if (family_id = find_font_family("papyrus")) {
738 } else {
739 throw parse_error(
740 std::format("{} Could not find any fantasy fallback font.", token_location(it, last, context.path)));
741 }
742 ++it;
743
744 } else if (*it == token::dstr) {
745 family_id = find_font_family(static_cast<std::string>(*it));
746 ++it;
747
748 } else {
749 throw parse_error(std::format(
750 "{} Expecting a font-family name or serif, sans-serif, monospace, cursive or fantasy.",
751 token_location(it, last, context.path)));
752 }
753 }
754
755 if (not family_id) {
756 throw parse_error(std::format(
757 "{} Could not find any of the fonts in this font-family declaration.", token_location(it, last, context.path)));
758 }
759
761 r.emplace_back(style_sheet_declaration_name::font_family, family_id);
762 return r;
763}
764
765template<typename It, std::sentinel_for<It> ItEnd>
766[[nodiscard]] constexpr std::vector<style_sheet_declaration>
767parse_style_sheet_font_style_declaration(It& it, ItEnd last, style_sheet_parser_context& context)
768{
770
771 if (it != last and *it == token::id and *it == "normal") {
772 r.emplace_back(style_sheet_declaration_name::font_style, font_style::normal);
773 } else if (it != last and *it == token::id and *it == "italic") {
774 r.emplace_back(style_sheet_declaration_name::font_style, font_style::italic);
775 } else if (it != last and *it == token::id and *it == "oblique") {
776 r.emplace_back(style_sheet_declaration_name::font_style, font_style::oblique);
777 } else {
778 throw parse_error(std::format(
779 "{} Expecting normal, italic or oblique as value of a font-style declaration.",
780 token_location(it, last, context.path)));
781 }
782
783 ++it;
784 return r;
785}
786
787template<typename It, std::sentinel_for<It> ItEnd>
788[[nodiscard]] constexpr std::vector<style_sheet_declaration>
789parse_style_sheet_font_weight_declaration(It& it, ItEnd last, style_sheet_parser_context& context)
790{
792
793 if (it != last and *it == token::id and *it == "thin") {
794 r.emplace_back(style_sheet_declaration_name::font_weight, font_weight::thin);
795 } else if (it != last and *it == token::id and *it == "extra-light") {
796 r.emplace_back(style_sheet_declaration_name::font_weight, font_weight::extra_light);
797 } else if (it != last and *it == token::id and *it == "light") {
798 r.emplace_back(style_sheet_declaration_name::font_weight, font_weight::light);
799 } else if (it != last and *it == token::id and (*it == "regular" or *it == "normal")) {
800 r.emplace_back(style_sheet_declaration_name::font_weight, font_weight::regular);
801 } else if (it != last and *it == token::id and *it == "medium") {
802 r.emplace_back(style_sheet_declaration_name::font_weight, font_weight::medium);
803 } else if (it != last and *it == token::id and *it == "semi-bold") {
804 r.emplace_back(style_sheet_declaration_name::font_weight, font_weight::semi_bold);
805 } else if (it != last and *it == token::id and *it == "bold") {
806 r.emplace_back(style_sheet_declaration_name::font_weight, font_weight::bold);
807 } else if (it != last and *it == token::id and *it == "extra-bold") {
808 r.emplace_back(style_sheet_declaration_name::font_weight, font_weight::extra_bold);
809 } else if (it != last and *it == token::id and *it == "black") {
810 r.emplace_back(style_sheet_declaration_name::font_weight, font_weight::black);
811 } else if (it != last and *it == token::id and *it == "extra-black") {
812 r.emplace_back(style_sheet_declaration_name::font_weight, font_weight::extra_black);
813
814 } else if (it != last and *it == token::integer) {
815 r.emplace_back(style_sheet_declaration_name::font_weight, font_weight_from_int(static_cast<int>(*it)));
816
817 } else {
818 throw parse_error(std::format(
819 "{} Expecting a integer or value of a font-weight declaration.", token_location(it, last, context.path)));
820 }
821
822 ++it;
823 return r;
824}
825
826template<typename It, std::sentinel_for<It> ItEnd>
827[[nodiscard]] constexpr std::vector<style_sheet_declaration>
828parse_style_sheet_margin_declarations(It& it, ItEnd last, style_sheet_parser_context& context)
829{
831
832 if (auto lengths = parse_style_sheet_lengths(it, last, context); not lengths.empty()) {
833 if (lengths.size() == 1) {
834 r.emplace_back(style_sheet_declaration_name::margin_top, lengths[0]);
835 r.emplace_back(style_sheet_declaration_name::margin_right, lengths[0]);
836 r.emplace_back(style_sheet_declaration_name::margin_bottom, lengths[0]);
837 r.emplace_back(style_sheet_declaration_name::margin_left, lengths[0]);
838 } else if (lengths.size() == 2) {
839 r.emplace_back(style_sheet_declaration_name::margin_top, lengths[0]);
840 r.emplace_back(style_sheet_declaration_name::margin_right, lengths[1]);
841 r.emplace_back(style_sheet_declaration_name::margin_bottom, lengths[0]);
842 r.emplace_back(style_sheet_declaration_name::margin_left, lengths[1]);
843 } else if (lengths.size() == 3) {
844 r.emplace_back(style_sheet_declaration_name::margin_top, lengths[0]);
845 r.emplace_back(style_sheet_declaration_name::margin_right, lengths[1]);
846 r.emplace_back(style_sheet_declaration_name::margin_bottom, lengths[2]);
847 r.emplace_back(style_sheet_declaration_name::margin_left, lengths[1]);
848 } else if (lengths.size() == 4) {
849 r.emplace_back(style_sheet_declaration_name::margin_top, lengths[0]);
850 r.emplace_back(style_sheet_declaration_name::margin_right, lengths[1]);
851 r.emplace_back(style_sheet_declaration_name::margin_bottom, lengths[2]);
852 r.emplace_back(style_sheet_declaration_name::margin_left, lengths[3]);
853 } else {
854 throw parse_error(std::format(
855 "{} Expect 1 to 4 length values when parsing \"margin\" declaration, got {}.",
856 token_location(it, last, context.path),
857 lengths.size()));
858 }
859 } else {
860 throw parse_error(std::format(
861 "{} Expect 1 to 4 length values when parsing \"margin\" declaration.", token_location(it, last, context.path)));
862 }
863
864 return r;
865}
866
867template<typename It, std::sentinel_for<It> ItEnd>
868[[nodiscard]] constexpr std::vector<style_sheet_declaration>
869parse_style_sheet_spacing_declarations(It& it, ItEnd last, style_sheet_parser_context& context)
870{
872
873 if (auto lengths = parse_style_sheet_lengths(it, last, context); not lengths.empty()) {
874 if (lengths.size() == 1) {
875 r.emplace_back(style_sheet_declaration_name::spacing_vertical, lengths[0]);
876 r.emplace_back(style_sheet_declaration_name::spacing_horizontal, lengths[0]);
877 } else if (lengths.size() == 2) {
878 r.emplace_back(style_sheet_declaration_name::spacing_vertical, lengths[0]);
879 r.emplace_back(style_sheet_declaration_name::spacing_horizontal, lengths[1]);
880 } else {
881 throw parse_error(std::format(
882 "{} Expect 1 or 2 length values when parsing \"spacing\" declaration, got {}.",
883 token_location(it, last, context.path),
884 lengths.size()));
885 }
886 } else {
887 throw parse_error(std::format(
888 "{} Expect 1 or 2 length values when parsing \"spacing\" declaration.", token_location(it, last, context.path)));
889 }
890
891 return r;
892}
893
894template<typename It, std::sentinel_for<It> ItEnd>
895[[nodiscard]] constexpr std::vector<style_sheet_declaration>
896parse_style_sheet_border_radius_declarations(It& it, ItEnd last, style_sheet_parser_context& context)
897{
899
900 if (auto lengths = parse_style_sheet_lengths(it, last, context); not lengths.empty()) {
901 if (lengths.size() == 1) {
902 r.emplace_back(style_sheet_declaration_name::border_top_left_radius, lengths[0]);
903 r.emplace_back(style_sheet_declaration_name::border_top_right_radius, lengths[0]);
904 r.emplace_back(style_sheet_declaration_name::border_bottom_left_radius, lengths[0]);
905 r.emplace_back(style_sheet_declaration_name::border_bottom_right_radius, lengths[0]);
906 } else if (lengths.size() == 2) {
907 r.emplace_back(style_sheet_declaration_name::border_top_left_radius, lengths[0]);
908 r.emplace_back(style_sheet_declaration_name::border_top_right_radius, lengths[1]);
909 r.emplace_back(style_sheet_declaration_name::border_bottom_left_radius, lengths[1]);
910 r.emplace_back(style_sheet_declaration_name::border_bottom_right_radius, lengths[0]);
911 } else if (lengths.size() == 4) {
912 r.emplace_back(style_sheet_declaration_name::border_top_left_radius, lengths[0]);
913 r.emplace_back(style_sheet_declaration_name::border_top_right_radius, lengths[1]);
914 r.emplace_back(style_sheet_declaration_name::border_bottom_left_radius, lengths[2]);
915 r.emplace_back(style_sheet_declaration_name::border_bottom_right_radius, lengths[3]);
916 } else {
917 throw parse_error(std::format(
918 "{} Expect 1, 2 or 4 length values when parsing \"border-radius\" declaration, got {}.",
919 token_location(it, last, context.path),
920 lengths.size()));
921 }
922 } else {
923 throw parse_error(std::format(
924 "{} Expect 1, 2 or 4 length values when parsing \"border-radius\" declaration.",
925 token_location(it, last, context.path)));
926 }
927
928 return r;
929}
930
931template<typename It, std::sentinel_for<It> ItEnd>
932[[nodiscard]] constexpr std::vector<style_sheet_declaration>
933parse_style_sheet_caret_color_declarations(It& it, ItEnd last, style_sheet_parser_context& context)
934{
936
937 if (auto colors = parse_style_sheet_colors(it, last, context); not colors.empty()) {
938 if (colors.size() == 1) {
939 r.emplace_back(style_sheet_declaration_name::caret_primary_color, colors[0]);
940 r.emplace_back(style_sheet_declaration_name::caret_secondary_color, colors[0]);
941 r.emplace_back(style_sheet_declaration_name::caret_overwrite_color, colors[0]);
942 r.emplace_back(style_sheet_declaration_name::caret_compose_color, colors[0]);
943 } else if (colors.size() == 2) {
944 r.emplace_back(style_sheet_declaration_name::caret_primary_color, colors[0]);
945 r.emplace_back(style_sheet_declaration_name::caret_secondary_color, colors[1]);
946 r.emplace_back(style_sheet_declaration_name::caret_overwrite_color, colors[0]);
947 r.emplace_back(style_sheet_declaration_name::caret_compose_color, colors[1]);
948 } else if (colors.size() == 3) {
949 r.emplace_back(style_sheet_declaration_name::caret_primary_color, colors[0]);
950 r.emplace_back(style_sheet_declaration_name::caret_secondary_color, colors[1]);
951 r.emplace_back(style_sheet_declaration_name::caret_overwrite_color, colors[2]);
952 r.emplace_back(style_sheet_declaration_name::caret_compose_color, colors[1]);
953 } else if (colors.size() == 4) {
954 r.emplace_back(style_sheet_declaration_name::caret_primary_color, colors[0]);
955 r.emplace_back(style_sheet_declaration_name::caret_secondary_color, colors[1]);
956 r.emplace_back(style_sheet_declaration_name::caret_overwrite_color, colors[2]);
957 r.emplace_back(style_sheet_declaration_name::caret_compose_color, colors[3]);
958 } else {
959 throw parse_error(std::format(
960 "{} Expect 1 to 4 color values when parsing \"caret-color\" declaration, got {}.",
961 token_location(it, last, context.path),
962 colors.size()));
963 }
964 } else {
965 throw parse_error(std::format(
966 "{} Expect 1 or 2 color values when parsing \"caret-color\" declaration.", token_location(it, last, context.path)));
967 }
968
969 return r;
970}
971
972template<typename It, std::sentinel_for<It> ItEnd>
973[[nodiscard]] constexpr std::optional<std::vector<style_sheet_declaration>>
974parse_style_sheet_macro_expansion(It& it, ItEnd last, style_sheet_parser_context& context)
975{
976 if (it.size() < 2 or it[0] != '@' or it[1] != token::id) {
977 return std::nullopt;
978 }
979
980 hilet name = static_cast<std::string>(it[1]);
981 it += 2;
982
984 if (auto declarations = context.get_macro(name)) {
985 r = std::move(*declarations);
986 } else {
987 throw parse_error(std::format("{} Trying to expand undeclared @macro {}.", token_location(it, last, context.path), name));
988 }
989
990 if (it == last or *it != ';') {
991 throw parse_error(std::format(
992 "{} Missing ';' after @macro {} expansion while parsing declaration.", token_location(it, last, context.path), name));
993 }
994
995 return {std::move(r)};
996}
997
998template<typename It, std::sentinel_for<It> ItEnd>
999[[nodiscard]] constexpr std::optional<std::vector<style_sheet_declaration>>
1000parse_style_sheet_declaration(It& it, ItEnd last, style_sheet_parser_context& context)
1001{
1002 // declaration := id ':' value ('!' "important")? ';'
1004
1005 if (it.size() < 2 or it[0] != token::id or it[1] != ':') {
1006 return std::nullopt;
1007 }
1008
1009 hilet name = static_cast<std::string>(*it);
1010 it += 2;
1011
1012 if (name == "margin") {
1013 r = parse_style_sheet_margin_declarations(it, last, context);
1014
1015 } else if (name == "spacing") {
1016 r = parse_style_sheet_spacing_declarations(it, last, context);
1017
1018 } else if (name == "border-radius") {
1019 r = parse_style_sheet_border_radius_declarations(it, last, context);
1020
1021 } else if (name == "caret-color") {
1022 r = parse_style_sheet_caret_color_declarations(it, last, context);
1023
1024 } else if (name == "font-family") {
1025 r = parse_style_sheet_font_family_declaration(it, last, context);
1026
1027 } else if (name == "font-style") {
1028 r = parse_style_sheet_font_style_declaration(it, last, context);
1029
1030 } else if (name == "font-weight") {
1031 r = parse_style_sheet_font_weight_declaration(it, last, context);
1032
1033 } else {
1034 hilet id = [&] {
1035 if (auto id = style_sheet_declaration_name_metadata.at_if(name)) {
1036 return *id;
1037 } else {
1038 throw parse_error(std::format("{} Invalid declaration name '{}'.", token_location(it, last, context.path), name));
1039 }
1040 }();
1041
1042 auto value = [&] {
1043 if (auto value = parse_style_sheet_value(it, last, context)) {
1044 return *value;
1045
1046 } else {
1047 throw parse_error(std::format(
1048 "{} Missing value after ':' while parsing {} declaration.", token_location(it, last, context.path), name));
1049 }
1050 }();
1051
1052 hilet value_mask = style_sheet_declaration_name_value_mask_metadata[id];
1053 if (std::holds_alternative<hi::dips>(value) and not to_bool(value_mask & style_sheet_value_mask::dips)) {
1054 throw parse_error(std::format(
1055 "{} Incorrect value type 'length:pt' for declaration of '{}'", token_location(it, last, context.path), name));
1056 } else if (std::holds_alternative<hi::pixels>(value) and not to_bool(value_mask & style_sheet_value_mask::pixels)) {
1057 throw parse_error(std::format(
1058 "{} Incorrect value type 'length:px' for declaration of '{}'", token_location(it, last, context.path), name));
1059 } else if (std::holds_alternative<hi::em_quads>(value) and not to_bool(value_mask & style_sheet_value_mask::em_quads)) {
1060 throw parse_error(std::format(
1061 "{} Incorrect value type 'length:em' for declaration of '{}'", token_location(it, last, context.path), name));
1062 } else if (std::holds_alternative<hi::color>(value) and not to_bool(value_mask & style_sheet_value_mask::color)) {
1063 throw parse_error(std::format(
1064 "{} Incorrect value type 'color' for declaration of '{}'", token_location(it, last, context.path), name));
1065 } else if (
1066 std::holds_alternative<hi::font_family_id>(value) and
1067 not to_bool(value_mask & style_sheet_value_mask::font_family_id)) {
1068 throw parse_error(std::format(
1069 "{} Incorrect value type 'font family id' for declaration of '{}'",
1070 token_location(it, last, context.path),
1071 name));
1072 } else if (
1073 std::holds_alternative<hi::font_weight>(value) and not to_bool(value_mask & style_sheet_value_mask::font_weight)) {
1074 throw parse_error(std::format(
1075 "{} Incorrect value type 'font weight' for declaration of '{}'", token_location(it, last, context.path), name));
1076 } else if (
1077 std::holds_alternative<hi::font_style>(value) and not to_bool(value_mask & style_sheet_value_mask::font_style)) {
1078 throw parse_error(std::format(
1079 "{} Incorrect value type 'font style' for declaration of '{}'", token_location(it, last, context.path), name));
1080 }
1081
1082 r.emplace_back(id, std::move(value));
1083 }
1084
1085 // Optional !important
1086 if (it.size() >= 2 and it[0] == '!' and it[1] == token::id and it[1] == "important") {
1087 for (auto& x : r) {
1088 x.important = true;
1089 }
1090 it += 2;
1091 }
1092
1093 if (it == last or *it != ';') {
1094 throw parse_error(std::format(
1095 "{} Missing ';' after value while parsing {} declaration.", token_location(it, last, context.path), name));
1096 }
1097 ++it;
1098
1099 return {std::move(r)};
1100}
1101
1102template<typename It, std::sentinel_for<It> ItEnd>
1103[[nodiscard]] constexpr std::optional<style_sheet_rule_set>
1104parse_style_sheet_rule_set(It& it, ItEnd last, style_sheet_parser_context& context)
1105{
1106 // rule_set := selector (':' state)* '{' declaration* '}'
1107 auto r = style_sheet_rule_set{};
1108
1109 if (auto selector = parse_style_sheet_selector(it, last, context)) {
1110 r.selector = *selector;
1111 } else {
1112 return std::nullopt;
1113 }
1114
1115 while (it != last and *it == ':') {
1116 ++it;
1117
1118 if (auto state_and_mask = parse_style_sheet_theme_state(it, last, context)) {
1119 r.state |= state_and_mask->first;
1120 r.state_mask |= state_and_mask->second;
1121
1122 } else if (auto language_tag = parse_style_sheet_theme_state_lang(it, last, context)) {
1123 r.language_mask = *language_tag;
1124
1125 } else if (auto phrasing_mask = parse_style_sheet_theme_state_phrasing(it, last, context)) {
1126 r.phrasing_mask = *phrasing_mask;
1127
1128 } else {
1129 throw parse_error(
1130 std::format("{} Expecting state-id after ':' in selector.", token_location(it, last, context.path)));
1131 }
1132 }
1133
1134 if (it != last and *it == '{') {
1135 ++it;
1136 } else {
1137 throw parse_error(std::format("{} Missing '{{' while parsing rule-set.", token_location(it, last, context.path)));
1138 }
1139
1140 while (it != last and *it != '}') {
1141 if (auto macro_declaration = parse_style_sheet_macro_expansion(it, last, context)) {
1142 r.declarations.insert(r.declarations.end(), macro_declaration->begin(), macro_declaration->end());
1143
1144 } else if (auto rule_declaration = parse_style_sheet_declaration(it, last, context)) {
1145 // A single declaration such as "margin" will generate multiple declarations:
1146 // "margin-left", "margin-right", "margin-top", "margin-bottom".
1147 r.declarations.insert(r.declarations.end(), rule_declaration->begin(), rule_declaration->end());
1148 } else {
1149 throw parse_error(
1150 std::format("{} Missing declaration while parsing rule-set.", token_location(it, last, context.path)));
1151 }
1152 }
1153
1154 if (it != last and *it == '}') {
1155 ++it;
1156 } else {
1157 throw parse_error(std::format("{} Missing '}}' while parsing rule-set.", token_location(it, last, context.path)));
1158 }
1159
1160 return {std::move(r)};
1161}
1162
1163template<typename It, std::sentinel_for<It> ItEnd>
1164[[nodiscard]] constexpr bool parse_style_sheet_color_at_rule(It& it, ItEnd last, style_sheet_parser_context& context)
1165{
1166 if (it.size() >= 2 and it[0] == '@' and it[1] == token::id and it[1] == "color") {
1167 it += 2;
1168
1169 if (it == last and *it != token::id) {
1170 throw parse_error(std::format("{} Expect name while parsing @color.", token_location(it, last, context.path)));
1171 }
1172
1173 hilet name = static_cast<std::string>(*it);
1174 ++it;
1175
1176 if (color::find(name) == nullptr) {
1177 throw parse_error(std::format(
1178 "{} Undefined color-name \"{}\" while parsing @color declaration.",
1179 token_location(it, last, context.path),
1180 name));
1181 }
1182
1183 if (it == last or *it != ':') {
1184 throw parse_error(std::format(
1185 "{} Missing ':' after color-name of @color {} declaration.", token_location(it, last, context.path), name));
1186 }
1187 ++it;
1188
1189 if (auto color = parse_style_sheet_color(it, last, context)) {
1190 if (not context.set_color(name, *color)) {
1191 throw parse_error(
1192 std::format("{} @color {} was already declared earlier.", token_location(it, last, context.path), name));
1193 }
1194 } else {
1195 throw parse_error(
1196 std::format("{} Missing color-value in @color {} declaration.", token_location(it, last, context.path), name));
1197 }
1198
1199 if (it == last or *it != ';') {
1200 throw parse_error(
1201 std::format("{} Missing ';' after @color {} declaration.", token_location(it, last, context.path), name));
1202 }
1203 ++it;
1204 return true;
1205
1206 } else {
1207 return false;
1208 }
1209}
1210
1211template<typename It, std::sentinel_for<It> ItEnd>
1212[[nodiscard]] constexpr bool parse_style_sheet_let_at_rule(It& it, ItEnd last, style_sheet_parser_context& context)
1213{
1214 // let := '@' "let" let-name ':' value ';'
1215 if (it.size() < 2 or it[0] != '@' or it[1] != token::id or it[1] != "let") {
1216 return false;
1217 }
1218 it += 2;
1219
1220 if (it == last or *it != token::id) {
1221 throw parse_error(std::format("{} Expect a name after @let.", token_location(it, last, context.path)));
1222 }
1223 hilet let_name = static_cast<std::string>(*it);
1224 ++it;
1225
1226 if (it == last or *it != ':') {
1227 throw parse_error(std::format("{} Expect ':' after @let {}.", token_location(it, last, context.path), let_name));
1228 }
1229 ++it;
1230
1231 if (auto value = parse_style_sheet_value(it, last, context)) {
1232 if (not context.set_let(let_name, *value)) {
1233 throw parse_error(
1234 std::format("{} @let {} was already declared earlier.", token_location(it, last, context.path), let_name));
1235 }
1236 } else {
1237 throw parse_error(std::format("{} Expect value after @let {} :.", token_location(it, last, context.path), let_name));
1238 }
1239
1240 if (it == last or *it != ';') {
1241 throw parse_error(
1242 std::format("{} Expect ';' after @let {} declaration.", token_location(it, last, context.path), let_name));
1243 }
1244 ++it;
1245 return true;
1246}
1247
1248template<typename It, std::sentinel_for<It> ItEnd>
1249[[nodiscard]] constexpr bool parse_style_sheet_macro_at_rule(It& it, ItEnd last, style_sheet_parser_context& context)
1250{
1251 // macro := '@' "macro" macro-name '{' declaration* '}'
1252
1253 if (it.size() < 2 or it[0] != '@' or it[1] != token::id or it[1] != "macro") {
1254 return false;
1255 }
1256
1257 it += 2;
1258
1259 if (it == last or *it != token::id) {
1260 throw parse_error(std::format("{} Expect a name after @macro.", token_location(it, last, context.path)));
1261 }
1262 hilet macro_name = static_cast<std::string>(*it);
1263 ++it;
1264
1265 if (it == last or *it != '{') {
1266 throw parse_error(std::format("{} Expect '{{' after a @macro {}.", token_location(it, last, context.path), macro_name));
1267 }
1268 ++it;
1269
1270 auto declarations = std::vector<style_sheet_declaration>{};
1271 while (it != last and *it != '}') {
1272 if (auto macro_declaration = parse_style_sheet_macro_expansion(it, last, context)) {
1273 declarations.insert(declarations.end(), macro_declaration->begin(), macro_declaration->end());
1274
1275 } else if (auto rule_declaration = parse_style_sheet_declaration(it, last, context)) {
1276 // A single declaration such as "margin" will generate multiple declarations:
1277 // "margin-left", "margin-right", "margin-top", "margin-bottom".
1278 declarations.insert(declarations.end(), rule_declaration->begin(), rule_declaration->end());
1279
1280 } else {
1281 throw parse_error(std::format(
1282 "{} Missing declaration while parsing @macro {}.", token_location(it, last, context.path), macro_name));
1283 }
1284 }
1285
1286 if (it == last or *it != '}') {
1287 throw parse_error(
1288 std::format("{} Expect '}}' after a @macro {} declarations.", token_location(it, last, context.path), macro_name));
1289 }
1290 ++it;
1291
1292 if (not context.set_macro(macro_name, std::move(declarations))) {
1293 throw parse_error(
1294 std::format("{} @macro {} was already declared earlier.", token_location(it, last, context.path), macro_name));
1295 }
1296 return true;
1297}
1298
1299template<typename It, std::sentinel_for<It> ItEnd>
1300[[nodiscard]] constexpr std::optional<std::string>
1301parse_style_sheet_name_at_rule(It& it, ItEnd last, style_sheet_parser_context& context)
1302{
1303 if (it.size() < 2 or it[0] != '@' or it[1] != token::id or it[1] != "name") {
1304 return std::nullopt;
1305 }
1306 it += 2;
1307
1308 if (it == last or *it != token::dstr) {
1309 throw parse_error(std::format("{} Expect string after @name.", token_location(it, last, context.path)));
1310 }
1311 auto r = static_cast<std::string>(*it);
1312 ++it;
1313
1314 if (it == last or *it != ';') {
1315 throw parse_error(std::format("{} Expect ';' after @name \"{}\".", token_location(it, last, context.path), r));
1316 }
1317 ++it;
1318
1319 return {std::move(r)};
1320}
1321
1322template<typename It, std::sentinel_for<It> ItEnd>
1323[[nodiscard]] constexpr std::optional<theme_mode>
1324parse_style_sheet_mode_at_rule(It& it, ItEnd last, style_sheet_parser_context& context)
1325{
1326 if (it.size() < 2 or it[0] != '@' or it[1] != token::id or it[1] != "mode") {
1327 return std::nullopt;
1328 }
1329 it += 2;
1330
1331 hilet r = [&] {
1332 if (it != last and *it == token::id and *it == "light") {
1333 return theme_mode::light;
1334 } else if (it != last and *it == token::id and *it == "dark") {
1335 return theme_mode::dark;
1336 } else {
1337 throw parse_error(std::format("{} Expect light or dark after @mode.", token_location(it, last, context.path)));
1338 }
1339 }();
1340 ++it;
1341
1342 if (it == last or *it != ';') {
1343 throw parse_error(std::format("{} Expect ';' after @name \"{}\".", token_location(it, last, context.path), r));
1344 }
1345 ++it;
1346
1347 return r;
1348}
1349
1350template<typename It, std::sentinel_for<It> ItEnd>
1351[[nodiscard]] constexpr std::optional<style_sheet> parse_style_sheet(It& it, ItEnd last, style_sheet_parser_context& context)
1352{
1353 // stylesheet := ( at_rule | rule_set )*
1354 auto r = style_sheet{};
1355
1356 if (auto name = parse_style_sheet_name_at_rule(it, last, context)) {
1357 r.name = std::move(*name);
1358 } else {
1359 throw parse_error(
1360 std::format("{} Did not find required @name as first declaration.", token_location(it, last, context.path)));
1361 }
1362
1363 if (auto mode = parse_style_sheet_mode_at_rule(it, last, context)) {
1364 r.mode = *mode;
1365 } else {
1366 throw parse_error(
1367 std::format("{} Did not find required @mode declaration after @name in the style sheet.", token_location(it, last, context.path)));
1368 }
1369
1370 while (it != last) {
1371 if (parse_style_sheet_color_at_rule(it, last, context)) {
1372 // colors are directly added to the context.
1373 } else if (parse_style_sheet_let_at_rule(it, last, context)) {
1374 // lets are directly added to the context.
1375 } else if (parse_style_sheet_macro_at_rule(it, last, context)) {
1376 // macros are directly added to the context.
1377 } else if (auto rule_set = parse_style_sheet_rule_set(it, last, context)) {
1378 r.rule_sets.push_back(*rule_set);
1379 } else {
1380 throw parse_error(std::format("{} Found unexpected token.", token_location(it, last, context.path)));
1381 }
1382 }
1383
1384 return r;
1385}
1386
1387} // namespace detail
1388
1389template<typename It, std::sentinel_for<It> ItEnd>
1390[[nodiscard]] constexpr style_sheet parse_style_sheet(It first, ItEnd last, std::filesystem::path const& path)
1391{
1392 auto lexer_it = lexer<lexer_config::css_style()>.parse(first, last);
1393 auto lookahead_it = make_lookahead_iterator<4>(lexer_it, std::default_sentinel);
1394
1395 auto context = detail::style_sheet_parser_context{};
1396 context.path = path;
1397 if (auto stylesheet = detail::parse_style_sheet(lookahead_it, std::default_sentinel, context)) {
1398 stylesheet->colors = context.move_colors();
1399 return *stylesheet;
1400 } else {
1401 throw parse_error(
1402 std::format("{} Could not parse style sheet file.", token_location(lookahead_it, std::default_sentinel, path)));
1403 }
1404}
1405
1406[[nodiscard]] constexpr style_sheet parse_style_sheet(std::string_view str, std::filesystem::path const& path)
1407{
1408 return parse_style_sheet(str.begin(), str.end(), path);
1409}
1410
1411[[nodiscard]] inline style_sheet parse_style_sheet(std::filesystem::path const& path)
1412{
1413 return parse_style_sheet(as_string_view(file_view{path}), path);
1414}
1415
1416}} // namespace hi::v1
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
float sRGB_gamma_to_linear(float u) noexcept
sRGB gamma to linear transfer function.
Definition sRGB.hpp:65
DOXYGEN BUG.
Definition algorithm.hpp:13
constexpr font_weight font_weight_from_int(numeric_integral auto rhs)
Convert a font weight value between 50 and 1000 to a font weight.
Definition font_weight.hpp:59
font_family_id find_font_family(std::string const &family_name) noexcept
Find font family id.
Definition font_book.hpp:186
geometry/margins.hpp
Definition cache.hpp:11
unit< em_length_tag, double > em_quads
Em-quad: A font's line-height.
Definition units.hpp:196
unit< px_length_tag, double > pixels
A physical pixel on a display.
Definition units.hpp:192
constexpr std::string token_location(It &it, ItEnd last, std::filesystem::path const &path) noexcept
Create a location string for error messages.
Definition token.hpp:165
unit< si_length_tag, double, std::ratio< 254, 960 '000 >::type > dips
Device Independent Pixels: 1/96 inch.
Definition units.hpp:188
unit< si_length_tag, double, std::ratio< 254, 10 '000 >::type > inches
Inch: 254 mm.
Definition units.hpp:181
unit< si_length_tag, double, std::ratio< 254, 720 '000 >::type > points
Points: 1/72 inch.
Definition units.hpp:177
@ italic
A font that is italic.
@ oblique
A font that is oblique.
@ normal
A font that is normal, non-italic.
This is a RGBA floating point color.
Definition color.hpp:44
static color * find(std::string const &name) noexcept
Find a color by name.
Definition color.hpp:310
Definition style_sheet.hpp:52
Definition style_sheet_parser.hpp:18
Exception thrown during parsing on an error.
Definition exception.hpp:50
T emplace_back(T... args)
T insert(T... args)
T lower_bound(T... args)
T move(T... args)
T push_back(T... args)
T size(T... args)
T what(T... args)