home
readme
diff
tree
note
docs
0commit dc020371c91e92834b99591eb1a02fecba1d5fac
1Author: Mason Wright <mason@lakefox.net>
2Date: Tue Sep 30 19:44:41 2025 -0600
3
4 Working on unit parsing for properties
5
6diff --git a/.swp b/.swp
7new file mode 100644
8index 0000000..ced872d
9Binary files /dev/null and b/.swp differ
10diff --git a/docs/content/features/.supported-css-selectors.md.swp b/docs/content/features/.supported-css-selectors.md.swp
11new file mode 100644
12index 0000000..17c75ef
13Binary files /dev/null and b/docs/content/features/.supported-css-selectors.md.swp differ
14diff --git a/docs/content/features/supported-css-selectors.md b/docs/content/features/supported-css-selectors.md
15index 26be4d8..bba1024 100755
16--- a/docs/content/features/supported-css-selectors.md
17+++ b/docs/content/features/supported-css-selectors.md
18@@ -186,7 +186,24 @@
19 | rgba | X | rgba(0-255, 0-255, 0-255 / 0-100%) | The alpha value MUST be a perent ranging from 0-100. You may also use spaces instead of commas |
20 | hsl | X | hsl(0-360deg, 0-100%, 0-100%) | You may use spaces instead of commas |
21 | hsla | X | hsla(0-360deg, 0-100%, 0-100% / 0-100%) | The alpha value MUST be a perent ranging from 0-100. You may also use spaces instead of commas |
22+| hwb | X | hwb(0-360deg, 0-100%, 0-100% / 0-100%) | The alpha value MUST be a perent ranging from 0-100. You may also use spaces instead of commas |
23+| lab | | | |
24+| lch | | | |
25+| oklab | | | |
26+| oklch | | | |
27+| color-mix | | | |
28
29+## Types
30+| Name | Support | Usage | Note |
31+|-|-|-|-|
32+| <absolute-size> | X | xx-small, x-small, small, medium, large, x-large, xx-large, xxx-large | Based off of relative EM value |
33+| <alpha-value> | | | |
34+| <angle-percentage> | | | |
35+| <angle> | | | |
36+| <baseline-position> | X | baseline, first baseline, last baseline | Singular baseline computes to first baseline |
37+| <basic-shape> | / | | Will be impleamented in the future |
38+| <blend-mode> | / | | Will be impleamented in the future |
39+| <box-edge> | X | content-box, padding-box, border-box, margin-box, fill-box, stroke-box, view-box | |
40
41 ## Other
42 | Name | Support | Usage | Note |
43diff --git a/include/.grim.h.swp b/include/.grim.h.swp
44index 4094bf3..8bc5a6d 100644
45Binary files a/include/.grim.h.swp and b/include/.grim.h.swp differ
46diff --git a/include/grim.h b/include/grim.h
47index 3bbc557..9c8162c 100755
48--- a/include/grim.h
49+++ b/include/grim.h
50@@ -155,12 +155,18 @@ enum class UnitType {
51 // Length
52 PX, EM, PERCENT, CM, CAP, CH, EX, IC, LH, RCAP, RCH, REM, REX, RIC, RLH, VH,
53 VW, VMAX, VMIN, VB, VI, CQW, CQH, CQI, CQB, CQMIN, CQMAX, MM, Q, IN, PC, PT,
54+ // Angle
55+ DEG, RAD, GRAD, TURN,
56+ // Baseline Position
57+ FIRST, LAST,
58+ // Box Edge
59+ BOX_EDGE, CONTENT, PADDING, BORDER, MARGIN, FILL, STROKE, VIEW,
60 // Function
61 CALC,
62 // Keyword
63 PRINT, SCREEN, TOKEN, FUNC,
64 // Property
65- BACKGROUND_COLOR
66+ BACKGROUND_COLOR, BASELINE
67 };
68
69 // 4 bytes
70@@ -168,7 +174,7 @@ union UnitValue {
71 UnitType ENUM;
72 int INT;
73 unsigned int UINT;
74- float DOUBLE;
75+ float FLOAT;
76 bool BOOL;
77 };
78
79diff --git a/src/.parser.cc.swp b/src/.parser.cc.swp
80new file mode 100644
81index 0000000..7e2ab09
82Binary files /dev/null and b/src/.parser.cc.swp differ
83diff --git a/src/parser.cc b/src/parser.cc
84index b8fbf08..9f2001d 100755
85--- a/src/parser.cc
86+++ b/src/parser.cc
87@@ -5,6 +5,7 @@
88 #include <unordered_map>
89 #include <cctype>
90 #include <algorithm>
91+#include <cmath>
92
93 std::unordered_map<std::string, std::string> parseAttributes(std::string_view token) {
94 std::unordered_map<std::string, std::string> attrs;
95@@ -334,6 +335,24 @@ std::unique_ptr<Node> parseHTML(std::istream& inputStream) {
96 return root;
97 }
98
99+
100+void typeCheck(std::string key, std::string value, Unit* property, std::vector<UnitType> allowedTypes) {
101+ for (size_t i = 0; i < property->children.size(); i++) {
102+ bool allowed = false;
103+ for (size_t j = 0; j < allowedTypes.size(); j++) {
104+ if (property->children[i]->type == allowedTypes[j]) {
105+ allowed = true;
106+ break;
107+ }
108+ }
109+
110+ if (!allowed) {
111+ std::cerr << "Invalid value of property: " << key << " Value: " << value << std::endl;
112+ exit();
113+ }
114+ }
115+}
116+
117 /*
118 | Parses CSS color strings:
119 | rgba(255 125 245 / 10)
120@@ -397,55 +416,201 @@ double hueToRGB(double p, double q, double t) {
121 return p;
122 }
123
124-Unit getUnit(std::string key, std::string value) {
125+std::vector<Unit> getUnit(std::string key, std::string value) {
126 // NOTES:
127 // | All colors are stored as rgba in hex format
128
129- Unit unit;
130- UnitValue uv;
131- std::string prefix = value.substr(0,4);
132-
133- if (value[0] == '#') {
134- uv.UINT = std::stoul(value.substr(1), nullptr, 16);
135- } else if (prefix == "rgba" || prefix == "rgb(") {
136- std::vector<double> colors = parseColor(value);
137- uv.UINT = (static_cast<int>(colors[0]) << 24) | (static_cast<int>(colors[1]) << 16) | (static_cast<int>(colors[2]) << 8) | static_cast<int>(255*colors[3]/100);
138- } else if (prefix == "hsl(" || prefix == "hsla") {
139- std::vector<double> colors = parseColor(value);
140- // Convert the parsed values to rgba
141- int r,g,b;
142- double h = colors[0]/360.0;
143- double s = colors[1]/100.0;
144- double l = colors[2]/100.0;
145- if (colors[1] == 0) {
146- // Gray scale
147- int lInt = l*255;
148- r = lInt;
149- g = lInt;
150- b = lInt;
151+ std::vector<Unit> units;
152+
153+ std::vector<std::string> parts;
154+ std::string buffer = "";
155+ bool inParentheses = false;
156+ bool inQuotes = false;
157+ char quoteType = '';
158+ bool escaped = false;
159+
160+ for (size_t i = 0; i < value.length(); i++) {
161+ char v = value[i];
162+ if (v == '\\' && !escaped) escaped = true;
163+
164+ if (v == '(' && !inParentheses && !inQuotes) inParentheses = true;
165+ else if (v == ')' && inParentheses && !inQuotes) inParentheses = false;
166+ else if (v == '"' || v == '\'' && !inParentheses) {
167+ if (inQuote && v == quoteType) {
168+ inQuotes = false;
169+ } else if (!inQuote) {
170+ quoteType = v;
171+ inQuotes = true;
172+ }
173+
174+ } else if (!inQuotes && !inParentheses && v == ' ') {
175+ // Split by spaces into parts
176+ parts.push_back(buffer);
177+ buffer.clear();
178+ } else if (!inQuotes && !inParentheses && v == ',' || i >= value.length()-1) {
179+ // EOL or a ','
180+
181+ // add the last one
182+ parts.push_back(buffer);
183+ buffer.clear();
184+
185+ // Main logic
186+ // Loop through each part and convert it into its type
187+ // the code around this point is breaking the css string
188+ // that may look like this:
189+ // 1px 3% 67vw 2px, rgba(123,456,789,0%)
190+ // into:
191+ // parts = [["1px", "3%", "67vw", "2px"],["rgba(123,456,789,0%)"]]
192+ //
193+ // the first for loop (j) will go through its parts and once its
194+ // done it will figure out what type of collection it is
195+ // in the example above it is a set of units for either margin or padding
196+ // and the second one is a color value
197+ // once it knows the type of collection it will add it to the units vector
198+
199+ for (size_t j = 0; j < parts.size(); j++) {
200+ Unit property;
201+ for (size_t k = 0; k < parts[j].size(); k++) {
202+ std::string part = parts[j][k];
203+ UnitType ut;
204+ UnitValue uv;
205+
206+ if (value.starts_with("#")) {
207+ ut = UnitType::HEX;
208+ uv.UINT = std::stoul(value.substr(1), nullptr, 16);
209+ } else if (value.starts_with("rgb")) {
210+ ut = UnitType::RGBA;
211+ std::vector<double> colors = parseColor(value);
212+ uv.UINT = (static_cast<int>(colors[0]) << 24) | (static_cast<int>(colors[1]) << 16) | (static_cast<int>(colors[2]) << 8) | static_cast<int>(255*colors[3]/100);
213+ } else if (value.starts_with("hsl")) {
214+ ut = UnitType::HSL;
215+ std::vector<double> colors = parseColor(value);
216+ // Convert the parsed values to rgba
217+ int r,g,b;
218+ double h = colors[0]/360.0;
219+ double s = colors[1]/100.0;
220+ double l = colors[2]/100.0;
221+ if (colors[1] == 0) {
222+ // Gray scale
223+ int lInt = l*255;
224+ r = lInt;
225+ g = lInt;
226+ b = lInt;
227+ } else {
228+ double q = (l < 0.5) ? l * (1.0 + s) : l + s - (l * s);
229+ double p = 2.0 * l - q;
230+
231+ r = static_cast<int>(hueToRGB(p, q, h + 1.0 / 3.0) * 255.0);
232+ g = static_cast<int>(hueToRGB(p, q, h) * 255.0);
233+ b = static_cast<int>(hueToRGB(p, q, h - 1.0 / 3.0) * 255.0);
234+ }
235+
236+ // convert hsl to rgb/hex
237+ uv.UINT = (r << 24) | (g << 16) | (b << 8) | static_cast<int>(255*colors[3]/100);
238+ } else if (value.starts_with("hwb")) {
239+ ut = UnitType::HWB;
240+ std::vector<double> colors = parseColor(value);
241+ // if gray scale or close
242+ int r,g,b;
243+ colors[1] /= 100;
244+ colors[2] /= 100;
245+ if (colors[1] + colors[2] > 1.0 - 1e-6) {
246+ r = static_cast<int>(colors[1] * 255.0);
247+ g = r;
248+ b = r;
249+ } else {
250+ double mod = std::fmod(colors[0], 360.0);
251+ if (mod < 0) mod += 360.0;
252+
253+ double i = std::floor(mod / 60.0);
254+ double f = (mod / 60.0) - i;
255+
256+ double rp = 0.0;
257+ double gp = 0.0;
258+ double bp = 0.0;
259+
260+ switch (static_cast<int>(i)) {
261+ case 0: rp = 1.0; gp = f; bp = 0.0; break;
262+ case 1: rp = 1.0 - f; gp = 1.0; bp = 0.0; break;
263+ case 2: rp = 0.0; gp = 1.0; bp = f; break;
264+ case 3: rp = 0.0; gp = 1.0 - f; bp = 1.0; break;
265+ case 4: rp = f; gp = 0.0; bp = 1.0; break;
266+ case 5: rp = 1.0; gp = 0.0; bp = 1.0 - f; break;
267+ default: // Should not happen for valid H
268+ rp = gp = bp = 0.0; break;
269+ }
270+
271+ double v = 1.0 - colors[1] - colors[2];
272+
273+ r = static_cast<int>(((rp * v) + colors[1])*255.0);
274+ g = static_cast<int>(((gp * v) + colors[1])*255.0);
275+ b = static_cast<int>(((bp * v) + colors[1])*255.0);
276+ }
277+
278+ uv.UINT = (r << 24) | (g << 16) | (b << 8) | static_cast<int>(255*colors[3]/100);
279+ } else if (value == "xx-small" && value == "x-small" && value == "small" && value == "medium" &&
280+ value == "large" && value == "x-large" && value == "xx-large" && value == "xxx-large" ) {
281+ // <absolute-size> read as a EM
282+ ut = UnitType::EM;
283+
284+ if (value == "xx-small") uv.FLOAT = 0.6;
285+ else if (value == "x-small") uv.FLOAT = 0.75;
286+ else if (value == "small") uv.FLOAT = 0.89;
287+ else if (value == "medium") uv.FLOAT = 1;
288+ else if (value == "large") uv.FLOAT = 1.2;
289+ else if (value == "x-large") uv.FLOAT = 1.5;
290+ else if (value == "xx-large") uv.FLOAT = 2;
291+ else if (value == "xxx-large") uv.FLOAT = 3;
292+ } else if (value.ends_with("baseline")) {
293+ ut = UnitType::BASELINE;
294+ if (value.starts_with("first")) uv.ENUM = UnitType::FIRST;
295+ else if (value.starts_with("last")) uv.ENUM = UnitType::LAST;
296+ else uv.ENUM = UnitType::FIRST;
297+ } else if (value.ends_with("-box")) {
298+ ut = UnitType::BOX_EDGE;
299+
300+ if (value.starts_with("content")) uv.ENUM = UnitType::CONTENT;
301+ else if (value.starts_with("padding")) uv.ENUM = UnitType::PADDING;
302+ else if (value.starts_with("border")) uv.ENUM = UnitType::BORDER;
303+ else if (value.starts_with("margin")) uv.ENUM = UnitType::MARGIN;
304+ else if (value.starts_with("fill")) uv.ENUM = UnitType::FILL;
305+ else if (value.starts_with("stroke")) uv.ENUM = UnitType::STROKE;
306+ else if (value.starts_with("view")) uv.ENUM = UnitType::VIEW;
307+ }
308+
309+ // What about cases like rotate(calc(10deg)) or even rgb should parse sub values
310+
311+ Unit unit;
312+ unit.type = ut;
313+ unit.value = uv;
314+
315+ property.children.push_back(unit);
316+ }
317+
318+ // ISSUE: need to find the property type before adding it
319+ // probally cut back on the super intensive parsing like the colors
320+ // and move to a lightweight recursive (lol) approach
321+ // the typeChecking of key values could also be moved to a different function that calls this one
322+
323+ if (key == "background-color") {
324+ typeCheck(key, value, property, {UnitType::RGB, UnitType::RGBA, UnitType::HSL, UnitType::HSLA, UnitType::HWB, UnitType::HEX})
325+ property.type == UnitType::BACKGROUND_COLOR;
326+ }
327+
328+ units.push_back(property);
329+ }
330+
331+ parts.clear();
332 } else {
333- double q = (l < 0.5) ? l * (1.0 + s) : l + s - (l * s);
334- double p = 2.0 * l - q;
335- std::cout << q << std::endl;
336- std::cout << p << std::endl;
337- std::cout << h << std::endl;
338- std::cout << s << std::endl;
339- std::cout << l << std::endl;
340- r = static_cast<int>(hueToRGB(p, q, h + 1.0 / 3.0) * 255.0);
341- g = static_cast<int>(hueToRGB(p, q, h) * 255.0);
342- b = static_cast<int>(hueToRGB(p, q, h - 1.0 / 3.0) * 255.0);
343+ buffer.push_back(v);
344 }
345-
346- // convert hsl to rgb/hex
347- uv.UINT = (r << 24) | (g << 16) | (b << 8) | static_cast<int>(255*colors[3]/100);
348+
349+ escaped = false;
350+ if (v == '\\') escaped = true;
351+
352 }
353
354- unit.value = uv;
355-
356- if (key == "background-color") {
357- unit.type = UnitType::BACKGROUND_COLOR;
358- }
359- return unit;
360+ return units;
361 }
362
363 void StyleHandler::parseStream(std::istream& inputStream) {
364diff --git a/tests/.css_unit.cc.swp b/tests/.css_unit.cc.swp
365new file mode 100644
366index 0000000..bc83178
367Binary files /dev/null and b/tests/.css_unit.cc.swp differ
368diff --git a/tests/css_unit.cc b/tests/css_unit.cc
369new file mode 100644
370index 0000000..239e7fa
371--- /dev/null
372+++ b/tests/css_unit.cc
373@@ -0,0 +1,52 @@
374+#include <catch_amalgamated.hpp>
375+#include "../include/grim.h"
376+#include "../include/parser.h"
377+#include <iostream>
378+#include <fstream>
379+
380+
381+TEST_CASE("getUnit can parse color values") {
382+
383+ unsigned int base = 0xff7dff1a;
384+
385+ SECTION("getUnit can parse hex") {
386+ Unit test = getUnit("background-color", "#ff7dff1a");
387+ INFO(test.value.UINT);
388+ REQUIRE(base == test.value.UINT);
389+ };
390+
391+ SECTION("getUnit can parse rgba") {
392+ Unit test = getUnit("background-color", "rgba(255 125 255 / 10%)");
393+ INFO(test.value.UINT);
394+ // Due to type casting 0.1 will return 25 not 26 or 19 vs 1a
395+ REQUIRE(0xff7dff19 == test.value.UINT);
396+ }
397+
398+ SECTION("getUnit can parse rgb") {
399+ Unit test = getUnit("background-color", "rgb(255 125 255)");
400+ INFO(test.value.UINT);
401+ // Due to type casting 0.1 will return 25 not 26 or 19 vs 1a
402+ REQUIRE(0xff7dff00 == test.value.UINT);
403+ }
404+
405+ SECTION("getUnit can parse hsl") {
406+ Unit test = getUnit("background-color", "hsl(300deg 100% 74.51%)");
407+ INFO(test.value.UINT);
408+ // Due to type casting 0.1 will return 25 not 26 or 19 vs 1a
409+ REQUIRE(0xff7afe00 == test.value.UINT);
410+ }
411+
412+ SECTION("getUnit can parse hsla") {
413+ Unit test = getUnit("background-color", "hsl(300deg 100% 74.51% / 10%)");
414+ INFO(test.value.UINT);
415+ // Due to type casting 0.1 will return 25 not 26 or 19 vs 1a
416+ REQUIRE(0xff7afe19 == test.value.UINT);
417+ }
418+
419+ SECTION("getUnit can parse hwb") {
420+ Unit test = getUnit("background-color", "hwb(80deg 20 50 / 50%)");
421+ INFO(test.value.UINT);
422+ // Due to type casting 0.1 will return 25 not 26 or 19 vs 1a
423+ REQUIRE(0x667f337f == test.value.UINT);
424+ }
425+}
426diff --git a/tests/query_select.cc b/tests/query_select.cc
427new file mode 100644
428index 0000000..aad133f
429--- /dev/null
430+++ b/tests/query_select.cc
431@@ -0,0 +1,28 @@
432+#include <catch_amalgamated.hpp>
433+#include "../include/grim.h"
434+#include "../include/parser.h"
435+#include <string>
436+#include <fstream>
437+#include <iostream>
438+
439+
440+
441+
442+TEST_CASE("testQuery", "") {
443+ std::string cssContent = R"~~~(
444+@container scroll-state(scrollable: top) {
445+}
446+@container sidebar (width > 400px) {
447+}
448+)~~~";
449+
450+ std::istringstream inputFile(cssContent);
451+
452+ Window window;
453+ window.CSS.parseStream(inputFile);
454+
455+ SECTION("@contianer") {
456+
457+ }
458+}
459+