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 \"dp\", \"pt\", \"mm\", \"cm\", \"dm\", \"m\", \"in\", \"px\" or \"em\" 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_border_radius_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::border_top_left_radius, lengths[0]);
876 r.emplace_back(style_sheet_declaration_name::border_top_right_radius, lengths[0]);
877 r.emplace_back(style_sheet_declaration_name::border_bottom_left_radius, lengths[0]);
878 r.emplace_back(style_sheet_declaration_name::border_bottom_right_radius, lengths[0]);
879 } else if (lengths.size() == 2) {
880 r.emplace_back(style_sheet_declaration_name::border_top_left_radius, lengths[0]);
881 r.emplace_back(style_sheet_declaration_name::border_top_right_radius, lengths[1]);
882 r.emplace_back(style_sheet_declaration_name::border_bottom_left_radius, lengths[1]);
883 r.emplace_back(style_sheet_declaration_name::border_bottom_right_radius, lengths[0]);
884 } else if (lengths.size() == 4) {
885 r.emplace_back(style_sheet_declaration_name::border_top_left_radius, lengths[0]);
886 r.emplace_back(style_sheet_declaration_name::border_top_right_radius, lengths[1]);
887 r.emplace_back(style_sheet_declaration_name::border_bottom_left_radius, lengths[2]);
888 r.emplace_back(style_sheet_declaration_name::border_bottom_right_radius, lengths[3]);
889 } else {
890 throw parse_error(std::format(
891 "{} Expect 1, 2 or 4 length values when parsing \"border-radius\" declaration, got {}.",
892 token_location(it, last, context.path),
893 lengths.size()));
894 }
895 } else {
896 throw parse_error(std::format(
897 "{} Expect 1, 2 or 4 length values when parsing \"border-radius\" declaration.",
898 token_location(it, last, context.path)));
899 }
900
901 return r;
902}
903
904template<typename It, std::sentinel_for<It> ItEnd>
905[[nodiscard]] constexpr std::vector<style_sheet_declaration>
906parse_style_sheet_caret_color_declarations(It& it, ItEnd last, style_sheet_parser_context& context)
907{
909
910 if (auto colors = parse_style_sheet_colors(it, last, context); not colors.empty()) {
911 if (colors.size() == 1) {
912 r.emplace_back(style_sheet_declaration_name::caret_primary_color, colors[0]);
913 r.emplace_back(style_sheet_declaration_name::caret_secondary_color, colors[0]);
914 r.emplace_back(style_sheet_declaration_name::caret_overwrite_color, colors[0]);
915 r.emplace_back(style_sheet_declaration_name::caret_compose_color, colors[0]);
916 } else if (colors.size() == 2) {
917 r.emplace_back(style_sheet_declaration_name::caret_primary_color, colors[0]);
918 r.emplace_back(style_sheet_declaration_name::caret_secondary_color, colors[1]);
919 r.emplace_back(style_sheet_declaration_name::caret_overwrite_color, colors[0]);
920 r.emplace_back(style_sheet_declaration_name::caret_compose_color, colors[1]);
921 } else if (colors.size() == 3) {
922 r.emplace_back(style_sheet_declaration_name::caret_primary_color, colors[0]);
923 r.emplace_back(style_sheet_declaration_name::caret_secondary_color, colors[1]);
924 r.emplace_back(style_sheet_declaration_name::caret_overwrite_color, colors[2]);
925 r.emplace_back(style_sheet_declaration_name::caret_compose_color, colors[1]);
926 } else if (colors.size() == 4) {
927 r.emplace_back(style_sheet_declaration_name::caret_primary_color, colors[0]);
928 r.emplace_back(style_sheet_declaration_name::caret_secondary_color, colors[1]);
929 r.emplace_back(style_sheet_declaration_name::caret_overwrite_color, colors[2]);
930 r.emplace_back(style_sheet_declaration_name::caret_compose_color, colors[3]);
931 } else {
932 throw parse_error(std::format(
933 "{} Expect 1 to 4 color values when parsing \"caret-color\" declaration, got {}.",
934 token_location(it, last, context.path),
935 colors.size()));
936 }
937 } else {
938 throw parse_error(std::format(
939 "{} Expect 1 or 2 color values when parsing \"caret-color\" declaration.", token_location(it, last, context.path)));
940 }
941
942 return r;
943}
944
945template<typename It, std::sentinel_for<It> ItEnd>
946[[nodiscard]] constexpr std::optional<std::vector<style_sheet_declaration>>
947parse_style_sheet_macro_expansion(It& it, ItEnd last, style_sheet_parser_context& context)
948{
949 if (it.size() < 2 or it[0] != '@' or it[1] != token::id) {
950 return std::nullopt;
951 }
952
953 hilet name = static_cast<std::string>(it[1]);
954 it += 2;
955
957 if (auto declarations = context.get_macro(name)) {
958 r = std::move(*declarations);
959 } else {
960 throw parse_error(std::format("{} Trying to expand undeclared @macro {}.", token_location(it, last, context.path), name));
961 }
962
963 if (it == last or *it != ';') {
964 throw parse_error(std::format(
965 "{} Missing ';' after @macro {} expansion while parsing declaration.", token_location(it, last, context.path), name));
966 }
967
968 return {std::move(r)};
969}
970
971template<typename It, std::sentinel_for<It> ItEnd>
972[[nodiscard]] constexpr std::optional<std::vector<style_sheet_declaration>>
973parse_style_sheet_declaration(It& it, ItEnd last, style_sheet_parser_context& context)
974{
975 // declaration := id ':' value ('!' "important")? ';'
977
978 if (it.size() < 2 or it[0] != token::id or it[1] != ':') {
979 return std::nullopt;
980 }
981
982 hilet name = static_cast<std::string>(*it);
983 it += 2;
984
985 if (name == "margin") {
986 r = parse_style_sheet_margin_declarations(it, last, context);
987
988 } else if (name == "border-radius") {
989 r = parse_style_sheet_border_radius_declarations(it, last, context);
990
991 } else if (name == "caret-color") {
992 r = parse_style_sheet_caret_color_declarations(it, last, context);
993
994 } else if (name == "font-family") {
995 r = parse_style_sheet_font_family_declaration(it, last, context);
996
997 } else if (name == "font-style") {
998 r = parse_style_sheet_font_style_declaration(it, last, context);
999
1000 } else if (name == "font-weight") {
1001 r = parse_style_sheet_font_weight_declaration(it, last, context);
1002
1003 } else {
1004 hilet id = [&] {
1005 if (auto id = style_sheet_declaration_name_metadata.at_if(name)) {
1006 return *id;
1007 } else {
1008 throw parse_error(std::format("{} Invalid declaration name '{}'.", token_location(it, last, context.path), name));
1009 }
1010 }();
1011
1012 auto value = [&] {
1013 if (auto value = parse_style_sheet_value(it, last, context)) {
1014 return *value;
1015
1016 } else {
1017 throw parse_error(std::format(
1018 "{} Missing value after ':' while parsing {} declaration.", token_location(it, last, context.path), name));
1019 }
1020 }();
1021
1022 hilet value_mask = style_sheet_declaration_name_value_mask_metadata[id];
1023 if (std::holds_alternative<hi::dips>(value) and not to_bool(value_mask & style_sheet_value_mask::dips)) {
1024 throw parse_error(std::format(
1025 "{} Incorrect value type 'length:pt' for declaration of '{}'", token_location(it, last, context.path), name));
1026 } else if (std::holds_alternative<hi::pixels>(value) and not to_bool(value_mask & style_sheet_value_mask::pixels)) {
1027 throw parse_error(std::format(
1028 "{} Incorrect value type 'length:px' for declaration of '{}'", token_location(it, last, context.path), name));
1029 } else if (std::holds_alternative<hi::em_quads>(value) and not to_bool(value_mask & style_sheet_value_mask::em_quads)) {
1030 throw parse_error(std::format(
1031 "{} Incorrect value type 'length:em' for declaration of '{}'", token_location(it, last, context.path), name));
1032 } else if (std::holds_alternative<hi::color>(value) and not to_bool(value_mask & style_sheet_value_mask::color)) {
1033 throw parse_error(std::format(
1034 "{} Incorrect value type 'color' for declaration of '{}'", token_location(it, last, context.path), name));
1035 } else if (
1036 std::holds_alternative<hi::font_family_id>(value) and
1037 not to_bool(value_mask & style_sheet_value_mask::font_family_id)) {
1038 throw parse_error(std::format(
1039 "{} Incorrect value type 'font family id' for declaration of '{}'",
1040 token_location(it, last, context.path),
1041 name));
1042 } else if (
1043 std::holds_alternative<hi::font_weight>(value) and not to_bool(value_mask & style_sheet_value_mask::font_weight)) {
1044 throw parse_error(std::format(
1045 "{} Incorrect value type 'font weight' for declaration of '{}'", token_location(it, last, context.path), name));
1046 } else if (
1047 std::holds_alternative<hi::font_style>(value) and not to_bool(value_mask & style_sheet_value_mask::font_style)) {
1048 throw parse_error(std::format(
1049 "{} Incorrect value type 'font style' for declaration of '{}'", token_location(it, last, context.path), name));
1050 }
1051
1052 r.emplace_back(id, std::move(value));
1053 }
1054
1055 // Optional !important
1056 if (it.size() >= 2 and it[0] == '!' and it[1] == token::id and it[1] == "important") {
1057 for (auto& x : r) {
1058 x.important = true;
1059 }
1060 it += 2;
1061 }
1062
1063 if (it == last or *it != ';') {
1064 throw parse_error(std::format(
1065 "{} Missing ';' after value while parsing {} declaration.", token_location(it, last, context.path), name));
1066 }
1067 ++it;
1068
1069 return {std::move(r)};
1070}
1071
1072template<typename It, std::sentinel_for<It> ItEnd>
1073[[nodiscard]] constexpr std::optional<style_sheet_rule_set>
1074parse_style_sheet_rule_set(It& it, ItEnd last, style_sheet_parser_context& context)
1075{
1076 // rule_set := selector (':' state)* '{' declaration* '}'
1077 auto r = style_sheet_rule_set{};
1078
1079 if (auto selector = parse_style_sheet_selector(it, last, context)) {
1080 r.selector = *selector;
1081 } else {
1082 return std::nullopt;
1083 }
1084
1085 while (it != last and *it == ':') {
1086 ++it;
1087
1088 if (auto state_and_mask = parse_style_sheet_theme_state(it, last, context)) {
1089 r.state |= state_and_mask->first;
1090 r.state_mask |= state_and_mask->second;
1091
1092 } else if (auto language_tag = parse_style_sheet_theme_state_lang(it, last, context)) {
1093 r.language_mask = *language_tag;
1094
1095 } else if (auto phrasing_mask = parse_style_sheet_theme_state_phrasing(it, last, context)) {
1096 r.phrasing_mask = *phrasing_mask;
1097
1098 } else {
1099 throw parse_error(
1100 std::format("{} Expecting state-id after ':' in selector.", token_location(it, last, context.path)));
1101 }
1102 }
1103
1104 if (it != last and *it == '{') {
1105 ++it;
1106 } else {
1107 throw parse_error(std::format("{} Missing '{{' while parsing rule-set.", token_location(it, last, context.path)));
1108 }
1109
1110 while (it != last and *it != '}') {
1111 if (auto macro_declaration = parse_style_sheet_macro_expansion(it, last, context)) {
1112 r.declarations.insert(r.declarations.end(), macro_declaration->begin(), macro_declaration->end());
1113
1114 } else if (auto rule_declaration = parse_style_sheet_declaration(it, last, context)) {
1115 // A single declaration such as "margin" will generate multiple declarations:
1116 // "margin-left", "margin-right", "margin-top", "margin-bottom".
1117 r.declarations.insert(r.declarations.end(), rule_declaration->begin(), rule_declaration->end());
1118 } else {
1119 throw parse_error(
1120 std::format("{} Missing declaration while parsing rule-set.", token_location(it, last, context.path)));
1121 }
1122 }
1123
1124 if (it != last and *it == '}') {
1125 ++it;
1126 } else {
1127 throw parse_error(std::format("{} Missing '}}' while parsing rule-set.", token_location(it, last, context.path)));
1128 }
1129
1130 return {std::move(r)};
1131}
1132
1133template<typename It, std::sentinel_for<It> ItEnd>
1134[[nodiscard]] constexpr bool parse_style_sheet_color_at_rule(It& it, ItEnd last, style_sheet_parser_context& context)
1135{
1136 if (it.size() >= 2 and it[0] == '@' and it[1] == token::id and it[1] == "color") {
1137 it += 2;
1138
1139 if (it == last and *it != token::id) {
1140 throw parse_error(std::format("{} Expect name while parsing @color.", token_location(it, last, context.path)));
1141 }
1142
1143 hilet name = static_cast<std::string>(*it);
1144 ++it;
1145
1146 if (color::find(name) == nullptr) {
1147 throw parse_error(std::format(
1148 "{} Undefined color-name \"{}\" while parsing @color declaration.",
1149 token_location(it, last, context.path),
1150 name));
1151 }
1152
1153 if (it == last or *it != ':') {
1154 throw parse_error(std::format(
1155 "{} Missing ':' after color-name of @color {} declaration.", token_location(it, last, context.path), name));
1156 }
1157 ++it;
1158
1159 if (auto color = parse_style_sheet_color(it, last, context)) {
1160 if (not context.set_color(name, *color)) {
1161 throw parse_error(
1162 std::format("{} @color {} was already declared earlier.", token_location(it, last, context.path), name));
1163 }
1164 } else {
1165 throw parse_error(
1166 std::format("{} Missing color-value in @color {} declaration.", token_location(it, last, context.path), name));
1167 }
1168
1169 if (it == last or *it != ';') {
1170 throw parse_error(
1171 std::format("{} Missing ';' after @color {} declaration.", token_location(it, last, context.path), name));
1172 }
1173 ++it;
1174 return true;
1175
1176 } else {
1177 return false;
1178 }
1179}
1180
1181template<typename It, std::sentinel_for<It> ItEnd>
1182[[nodiscard]] constexpr bool parse_style_sheet_let_at_rule(It& it, ItEnd last, style_sheet_parser_context& context)
1183{
1184 // let := '@' "let" let-name ':' value ';'
1185 if (it.size() < 2 or it[0] != '@' or it[1] != token::id or it[1] != "let") {
1186 return false;
1187 }
1188 it += 2;
1189
1190 if (it == last or *it != token::id) {
1191 throw parse_error(std::format("{} Expect a name after @let.", token_location(it, last, context.path)));
1192 }
1193 hilet let_name = static_cast<std::string>(*it);
1194 ++it;
1195
1196 if (it == last or *it != ':') {
1197 throw parse_error(std::format("{} Expect ':' after @let {}.", token_location(it, last, context.path), let_name));
1198 }
1199 ++it;
1200
1201 if (auto value = parse_style_sheet_value(it, last, context)) {
1202 if (not context.set_let(let_name, *value)) {
1203 throw parse_error(
1204 std::format("{} @let {} was already declared earlier.", token_location(it, last, context.path), let_name));
1205 }
1206 } else {
1207 throw parse_error(std::format("{} Expect value after @let {} :.", token_location(it, last, context.path), let_name));
1208 }
1209
1210 if (it == last or *it != ';') {
1211 throw parse_error(
1212 std::format("{} Expect ';' after @let {} declaration.", token_location(it, last, context.path), let_name));
1213 }
1214 ++it;
1215 return true;
1216}
1217
1218template<typename It, std::sentinel_for<It> ItEnd>
1219[[nodiscard]] constexpr bool parse_style_sheet_macro_at_rule(It& it, ItEnd last, style_sheet_parser_context& context)
1220{
1221 // macro := '@' "macro" macro-name '{' declaration* '}'
1222
1223 if (it.size() < 2 or it[0] != '@' or it[1] != token::id or it[1] != "macro") {
1224 return false;
1225 }
1226
1227 it += 2;
1228
1229 if (it == last or *it != token::id) {
1230 throw parse_error(std::format("{} Expect a name after @macro.", token_location(it, last, context.path)));
1231 }
1232 hilet macro_name = static_cast<std::string>(*it);
1233 ++it;
1234
1235 if (it == last or *it != '{') {
1236 throw parse_error(std::format("{} Expect '{{' after a @macro {}.", token_location(it, last, context.path), macro_name));
1237 }
1238 ++it;
1239
1240 auto declarations = std::vector<style_sheet_declaration>{};
1241 while (it != last and *it != '}') {
1242 if (auto macro_declaration = parse_style_sheet_macro_expansion(it, last, context)) {
1243 declarations.insert(declarations.end(), macro_declaration->begin(), macro_declaration->end());
1244
1245 } else if (auto rule_declaration = parse_style_sheet_declaration(it, last, context)) {
1246 // A single declaration such as "margin" will generate multiple declarations:
1247 // "margin-left", "margin-right", "margin-top", "margin-bottom".
1248 declarations.insert(declarations.end(), rule_declaration->begin(), rule_declaration->end());
1249
1250 } else {
1251 throw parse_error(std::format(
1252 "{} Missing declaration while parsing @macro {}.", token_location(it, last, context.path), macro_name));
1253 }
1254 }
1255
1256 if (it == last or *it != '}') {
1257 throw parse_error(
1258 std::format("{} Expect '}}' after a @macro {} declarations.", token_location(it, last, context.path), macro_name));
1259 }
1260 ++it;
1261
1262 if (not context.set_macro(macro_name, std::move(declarations))) {
1263 throw parse_error(
1264 std::format("{} @macro {} was already declared earlier.", token_location(it, last, context.path), macro_name));
1265 }
1266 return true;
1267}
1268
1269template<typename It, std::sentinel_for<It> ItEnd>
1270[[nodiscard]] constexpr std::optional<std::string>
1271parse_style_sheet_name_at_rule(It& it, ItEnd last, style_sheet_parser_context& context)
1272{
1273 if (it.size() < 2 or it[0] != '@' or it[1] != token::id or it[1] != "name") {
1274 return std::nullopt;
1275 }
1276 it += 2;
1277
1278 if (it == last or *it != token::dstr) {
1279 throw parse_error(std::format("{} Expect string after @name.", token_location(it, last, context.path)));
1280 }
1281 auto r = static_cast<std::string>(*it);
1282 ++it;
1283
1284 if (it == last or *it != ';') {
1285 throw parse_error(std::format("{} Expect ';' after @name \"{}\".", token_location(it, last, context.path), r));
1286 }
1287 ++it;
1288
1289 return {std::move(r)};
1290}
1291
1292template<typename It, std::sentinel_for<It> ItEnd>
1293[[nodiscard]] constexpr std::optional<theme_mode>
1294parse_style_sheet_mode_at_rule(It& it, ItEnd last, style_sheet_parser_context& context)
1295{
1296 if (it.size() < 2 or it[0] != '@' or it[1] != token::id or it[1] != "mode") {
1297 return std::nullopt;
1298 }
1299 it += 2;
1300
1301 hilet r = [&] {
1302 if (it != last and *it == token::id and *it == "light") {
1303 return theme_mode::light;
1304 } else if (it != last and *it == token::id and *it == "dark") {
1305 return theme_mode::dark;
1306 } else {
1307 throw parse_error(std::format("{} Expect light or dark after @mode.", token_location(it, last, context.path)));
1308 }
1309 }();
1310 ++it;
1311
1312 if (it == last or *it != ';') {
1313 throw parse_error(std::format("{} Expect ';' after @name \"{}\".", token_location(it, last, context.path), r));
1314 }
1315 ++it;
1316
1317 return r;
1318}
1319
1320template<typename It, std::sentinel_for<It> ItEnd>
1321[[nodiscard]] constexpr std::optional<style_sheet> parse_style_sheet(It& it, ItEnd last, style_sheet_parser_context& context)
1322{
1323 // stylesheet := ( at_rule | rule_set )*
1324 auto r = style_sheet{};
1325
1326 if (auto name = parse_style_sheet_name_at_rule(it, last, context)) {
1327 r.name = std::move(*name);
1328 } else {
1329 throw parse_error(
1330 std::format("{} Did not find required @name as first declaration.", token_location(it, last, context.path)));
1331 }
1332
1333 if (auto mode = parse_style_sheet_mode_at_rule(it, last, context)) {
1334 r.mode = *mode;
1335 } else {
1336 throw parse_error(
1337 std::format("{} Did not find required @mode declaration after @name in the style sheet.", token_location(it, last, context.path)));
1338 }
1339
1340 while (it != last) {
1341 if (parse_style_sheet_color_at_rule(it, last, context)) {
1342 // colors are directly added to the context.
1343 } else if (parse_style_sheet_let_at_rule(it, last, context)) {
1344 // lets are directly added to the context.
1345 } else if (parse_style_sheet_macro_at_rule(it, last, context)) {
1346 // macros are directly added to the context.
1347 } else if (auto rule_set = parse_style_sheet_rule_set(it, last, context)) {
1348 r.rule_sets.push_back(*rule_set);
1349 } else {
1350 throw parse_error(std::format("{} Found unexpected token.", token_location(it, last, context.path)));
1351 }
1352 }
1353
1354 return r;
1355}
1356
1357} // namespace detail
1358
1359template<typename It, std::sentinel_for<It> ItEnd>
1360[[nodiscard]] constexpr style_sheet parse_style_sheet(It first, ItEnd last, std::filesystem::path const& path)
1361{
1362 auto lexer_it = lexer<lexer_config::css_style()>.parse(first, last);
1363 auto lookahead_it = make_lookahead_iterator<4>(lexer_it, std::default_sentinel);
1364
1365 auto context = detail::style_sheet_parser_context{};
1366 context.path = path;
1367 if (auto stylesheet = detail::parse_style_sheet(lookahead_it, std::default_sentinel, context)) {
1368 stylesheet->colors = context.move_colors();
1369 return *stylesheet;
1370 } else {
1371 throw parse_error(
1372 std::format("{} Could not parse style sheet file.", token_location(lookahead_it, std::default_sentinel, path)));
1373 }
1374}
1375
1376[[nodiscard]] constexpr style_sheet parse_style_sheet(std::string_view str, std::filesystem::path const& path)
1377{
1378 return parse_style_sheet(str.begin(), str.end(), path);
1379}
1380
1381[[nodiscard]] inline style_sheet parse_style_sheet(std::filesystem::path const& path)
1382{
1383 return parse_style_sheet(as_string_view(file_view{path}), path);
1384}
1385
1386}} // 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:311
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)