home readme diff tree note docs

0commit d2128331bf02d3d26e8b8770cf6af9712f3e2933
1Author: lakefox <mason@lakefox.net>
2Date:   Thu Jan 1 11:43:21 2026 -0700
3
4    Renamed WindowUnitLengthValue to UnitLengthValue now uses Nodes. Added tests and make run_
5
6diff --git a/Makefile b/Makefile
7index 435bf3b..c295710 100755
8--- a/Makefile
9+++ b/Makefile
10@@ -20,12 +20,22 @@ build/parser.o: src/parser.cc include/parser.h | build
11 
12 # --- Tests ---
13 
14-all_tests: build/html_node_test build/css_selector_test build/html_parser_test build/query_select_test
15-	build/html_node_test --skip-benchmarks
16-	build/css_selector_test --skip-benchmarks
17-	build/html_parser_test --skip-benchmarks
18-	build/query_select_test --skip-benchmarks
19-	build/css_unit_test --skip-benchmarks
20+TEST_BINARIES = build/html_node_test build/css_selector_test \
21+                build/html_parser_test build/query_select_test \
22+                build/css_unit_test build/css_parser_test \
23+                build/windowunitlength_test
24+
25+all_tests: $(TEST_BINARIES)
26+	@for test in $(TEST_BINARIES); do \
27+		echo "Running $$test..."; \
28+		$$test --skip-benchmarks; \
29+	done
30+
31+# This target clears the screen, builds the specific test/playground, and runs it
32+run_%: build/%
33+	@clear
34+	@echo "--- Running $* ---"
35+	@./build/$* --skip-benchmarks
36 
37 build/css_unit_test: tests/css_unit.cc build/grim.o build/catch.o build/parser.o | build
38 	${CC} ${CCFLAGS} -Iinclude/ tests/css_unit.cc build/grim.o build/catch.o build/parser.o -o build/css_unit_test 
39@@ -45,6 +55,10 @@ build/css_parser_test: tests/css_parser.cc build/grim.o build/parser.o build/cat
40 build/query_select_test: tests/query_select.cc build/grim.o build/parser.o build/catch.o | build
41 	${CC} ${CCFLAGS} -Iinclude/ tests/query_select.cc build/grim.o build/parser.o build/catch.o -o build/query_select_test
42 
43+build/windowunitlength_test: tests/windowunitlength.cc build/grim.o build/parser.o build/catch.o | build
44+	${CC} ${CCFLAGS} -Iinclude/ tests/windowunitlength.cc build/grim.o build/parser.o build/catch.o -o build/windowunitlength_test
45+
46+
47 build/catch.o: ./include/catch_amalgamated.cpp ./include/catch_amalgamated.hpp | build
48 	${CC} ${CCFLAGS} -Iinclude/ -c include/catch_amalgamated.cpp -o build/catch.o
49 
50diff --git a/include/grim.h b/include/grim.h
51index e7ef8c6..5615570 100755
52--- a/include/grim.h
53+++ b/include/grim.h
54@@ -96,6 +96,8 @@ enum class UnitType : short {
55 	// Transform Functions
56 		MATRIX,	MATRIX3D, PERSPECTIVE, ROTATE, ROTATE3D, ROTATEX, ROTATEY, ROTATEZ, SCALE, SCALE3D, SCALEX, SCALEY, SCALEZ, TRANSLATE, TRANSLATE3D, TRANSLATEX, TRANSLATEY, TRANSLATEZ, 
57 		SKEW, SKEWX, SKEWY,
58+	// Rounding
59+		NEAREST, UP, DOWN, TO_ZERO,
60 
61 	// Keyword
62 		PRINT, SCREEN, TOKEN, FUNC, GROUP, NO_SUPPORT, IMAGE, STRING
63@@ -465,6 +467,6 @@ For the adapter, it will take a window as a argument to set the adapters capabil
64 Window createWindow();
65 
66 
67-float WindowUnitLengthValue(Window*, const std::vector<Unit>&, size_t, size_t);
68+float UnitLengthValue(Node*, const std::vector<Unit>&, size_t, size_t);
69 
70 #endif // NODE_H
71diff --git a/playground/css_unit.cc b/playground/css_unit.cc
72index 148320a..c064e68 100644
73--- a/playground/css_unit.cc
74+++ b/playground/css_unit.cc
75@@ -4,7 +4,7 @@
76 #include <fstream>
77 
78 int main() {
79-	std::vector<Unit> test = getUnit("min(100px, 12rem)");
80+	std::vector<Unit> test = getUnit("attr(size px)");
81 
82 	std::cout << "Done" << std::endl;
83 }
84diff --git a/playground/window.cc b/playground/window.cc
85index a76ef6e..d8a79a5 100644
86--- a/playground/window.cc
87+++ b/playground/window.cc
88@@ -41,14 +41,13 @@ int main() {
89 	Window window = createWindow();
90 	window.parseHTML(ss);
91 
92-//	Node document = window.document;
93+	Node document = window.document;
94 
95-	window.setWidth(1000);
96-	window.setHeight(700);
97-	window.setEM(16);
98+	document.cacheWidth(1000);
99+	document.cacheHeight(700);
100+	document.cacheEM(16);
101 
102-	// std::vector<Unit> units = getUnit("clamp(100px, calc(30% / 2rem + 10px), 900px)");
103-	std::vector<Unit> units = getUnit("clamp(100px, calc(30% / 2rem + 10px), 900px)");
104+	std::vector<Unit> units = getUnit("mod(13px, 5px)");
105 
106-	std::cout << WindowUnitLengthValue(&window, units, 0, units.size()) << std::endl;
107+	std::cout << UnitLengthValue(&document, units, 0, units.size()) << std::endl;
108 }
109diff --git a/src/grim.cc b/src/grim.cc
110index 1dcbc67..23940a2 100755
111--- a/src/grim.cc
112+++ b/src/grim.cc
113@@ -12,6 +12,7 @@
114 #include <optional>
115 #include <stdexcept>
116 #include <limits>
117+#include <cmath>
118 
119 // +------------------------------------------------------+
120 // |(TOC) Table of contents                               |
121@@ -965,7 +966,7 @@ std::vector<Range> extractUnits(const std::vector<Unit>& units, size_t start, si
122 /*
123 	CAUTION: These values are only correct for top level media queries
124 */
125-float WindowUnitLengthValue(Window* window, const std::vector<Unit>& units, size_t start, size_t end) {
126+float UnitLengthValue(Node* n, const std::vector<Unit>& units, size_t start, size_t end) {
127 	float sum = 0;
128 	UnitType op = UnitType::ADD;
129 
130@@ -980,6 +981,7 @@ float WindowUnitLengthValue(Window* window, const std::vector<Unit>& units, size
131 		float v2 = 0;
132 		float v3 = 0;
133 		std::vector<Range> extracted;
134+		UnitType ut;
135 
136 		switch (type) {
137 			case UnitType::ADD:
138@@ -988,8 +990,12 @@ float WindowUnitLengthValue(Window* window, const std::vector<Unit>& units, size
139 			case UnitType::MULT:
140 				op = type;
141 				break;
142+			// DEG, TURN are not length values but are treated as generic units (PX)
143+			case UnitType::DEG:
144+			case UnitType::TURN:
145+			case UnitType::NUMBER:
146+				// NUMBER is any untyped numbers "2". Treat as a pixel
147 			case UnitType::PX:
148-				std::cout << "PX: " << unit.value.FLOAT << std::endl;
149 				value = unit.value.FLOAT;
150 				break;
151 			case UnitType::CM:
152@@ -1021,27 +1027,27 @@ float WindowUnitLengthValue(Window* window, const std::vector<Unit>& units, size
153 			case UnitType::RCH:
154 			case UnitType::RIC:
155 			case UnitType::RLH:
156-				value = unit.value.FLOAT*window->getEM();
157+				value = unit.value.FLOAT*n->getEM();
158 				break;
159 			case UnitType::EX:
160 			case UnitType::REX:
161-				value = unit.value.FLOAT*(window->getEM()/2);
162+				value = unit.value.FLOAT*(n->getEM()/2);
163 				break;
164 			case UnitType::CQH:
165 			case UnitType::VH:
166-				value = window->getHeight()*(unit.value.FLOAT/100);
167+				value = n->getHeight()*(unit.value.FLOAT/100);
168 				break;
169 			case UnitType::CQW:
170 			case UnitType::CQI:
171 			case UnitType::CQB:
172 			case UnitType::PERCENT:
173 			case UnitType::VW:	
174-				value = window->getWidth()*(unit.value.FLOAT/100);
175+				value = n->getWidth()*(unit.value.FLOAT/100);
176 				break;
177 			case UnitType::VMAX:
178 			case UnitType::CQMAX:
179-				w = window->getWidth();
180-				h = window->getHeight();
181+				w = n->getWidth();
182+				h = n->getHeight();
183 				if (w > h) {
184 					value = unit.value.FLOAT*w;
185 				} else {
186@@ -1050,8 +1056,8 @@ float WindowUnitLengthValue(Window* window, const std::vector<Unit>& units, size
187 				break;
188 			case UnitType::VMIN:
189 			case UnitType::CQMIN:
190-				w = window->getWidth();
191-				h = window->getHeight();
192+				w = n->getWidth();
193+				h = n->getHeight();
194 				if (w < h) {
195 					value = unit.value.FLOAT*w;
196 				} else {
197@@ -1060,10 +1066,10 @@ float WindowUnitLengthValue(Window* window, const std::vector<Unit>& units, size
198 				break;
199 			case UnitType::VB:
200 			case UnitType::VI:
201-				value = unit.value.FLOAT*window->getWidth();
202+				value = unit.value.FLOAT*n->getWidth();
203 				break;
204 			case UnitType::CALC:
205-				value = WindowUnitLengthValue(window, units, i+1, i+1+unit.value.INT);
206+				value = UnitLengthValue(n, units, i+1, i+1+unit.value.INT);
207 				i += unit.value.INT;
208 				break;
209 			case UnitType::MIN:
210@@ -1072,7 +1078,7 @@ float WindowUnitLengthValue(Window* window, const std::vector<Unit>& units, size
211 				v1 = std::numeric_limits<float>::max(); // Store of the min value
212 
213 				for (size_t j = 0; j < extracted.size(); j++) {
214-					v2 = WindowUnitLengthValue(window, units, extracted[j].start, extracted[j].end);
215+					v2 = UnitLengthValue(n, units, extracted[j].start, extracted[j].end);
216 
217 					if (v2 < v1) {
218 						v1 = v2;
219@@ -1089,7 +1095,7 @@ float WindowUnitLengthValue(Window* window, const std::vector<Unit>& units, size
220 				v1 = std::numeric_limits<float>::min(); // Store of the max value
221 
222 				for (size_t j = 0; j < extracted.size(); j++) {
223-					v2 = WindowUnitLengthValue(window, units, extracted[j].start, extracted[j].end);
224+					v2 = UnitLengthValue(n, units, extracted[j].start, extracted[j].end);
225 
226 					if (v2 > v1) {
227 						v1 = v2;
228@@ -1104,13 +1110,9 @@ float WindowUnitLengthValue(Window* window, const std::vector<Unit>& units, size
229 				extracted = extractUnits(units, i+1, i+1+unit.value.INT);
230 
231 				if (extracted.size() >= 3) {
232-					v1 = WindowUnitLengthValue(window, units, extracted[0].start, extracted[0].end);
233-					v2 = WindowUnitLengthValue(window, units, extracted[1].start, extracted[1].end);
234-					v3 = WindowUnitLengthValue(window, units, extracted[2].start, extracted[2].end);
235-
236-					std::cout << "V1: " << v1 << std::endl;
237-					std::cout << "V2: " << v2 << std::endl;
238-					std::cout << "V3: " << v3 << std::endl;
239+					v1 = UnitLengthValue(n, units, extracted[0].start, extracted[0].end);
240+					v2 = UnitLengthValue(n, units, extracted[1].start, extracted[1].end);
241+					v3 = UnitLengthValue(n, units, extracted[2].start, extracted[2].end);
242 
243 					if (v2 < v1) {
244 						v2 = v1;
245@@ -1118,16 +1120,202 @@ float WindowUnitLengthValue(Window* window, const std::vector<Unit>& units, size
246 					if (v2 > v3) {
247 						v2 = v3;
248 					}
249-				}
250 
251-				value = v2;
252-			
253+					value = v2;
254+				}
255 				
256 				i += unit.value.INT;
257 				break;
258 			case UnitType::ROUND:
259+				extracted = extractUnits(units, i+1, i+1+unit.value.INT);
260+
261+				ut = UnitType::NEAREST;
262+
263+				if (extracted.size() == 3) {
264+					ut = units[extracted[0].start].type;
265+
266+					v1 = UnitLengthValue(n, units, extracted[1].start, extracted[1].end);
267+					v2 = UnitLengthValue(n, units, extracted[2].start, extracted[2].end);
268+				} else if (extracted.size() == 2) {
269+					ut = UnitType::NEAREST;
270+
271+					v1 = UnitLengthValue(n, units, extracted[0].start, extracted[0].end);
272+					v2 = UnitLengthValue(n, units, extracted[1].start, extracted[1].end);
273+				} else if (extracted.size() == 1) {
274+					ut = UnitType::NEAREST;
275+
276+					v1 = UnitLengthValue(n, units, extracted[0].start, extracted[0].end);
277+					v2 = 1.0f;
278+				}
279+
280+
281+				if (ut == UnitType::NEAREST) {
282+					value = std::round(v1/v2)*v2; 
283+				} else if (ut == UnitType::UP) {
284+					value = std::ceil(v1/v2)*v2; 
285+				} else if (ut == UnitType::DOWN) {
286+					value = std::floor(v1/v2)*v2; 
287+				} else if (ut == UnitType::TO_ZERO) {
288+					value = std::trunc(v1/v2)*v2; 
289+				}
290+
291+				i += unit.value.INT;
292+				break;
293+			case UnitType::MOD:
294+				extracted = extractUnits(units, i+1, i+1+unit.value.INT);
295+
296+				if (extracted.size() == 2) {
297+					v1 = UnitLengthValue(n, units, extracted[0].start, extracted[0].end);
298+					v2 = UnitLengthValue(n, units, extracted[1].start, extracted[1].end);
299+
300+					value = std::fmod(v1, v2);
301+				}
302+
303+				i += unit.value.INT;
304+				break;
305+			case UnitType::PROGRESS:
306+				extracted = extractUnits(units, i+1, i+1+unit.value.INT);
307+
308+				if (extracted.size() >= 3) {
309+					v1 = UnitLengthValue(n, units, extracted[0].start, extracted[0].end);
310+					v2 = UnitLengthValue(n, units, extracted[1].start, extracted[1].end);
311+					v3 = UnitLengthValue(n, units, extracted[2].start, extracted[2].end);
312+
313+					// (progress - progress start) / (progress end - progress start)
314+					value = (v1-v2)/(v3-v2);
315+				}
316+
317+				
318+				i += unit.value.INT;
319+				break;
320+			case UnitType::REMD:
321+				extracted = extractUnits(units, i+1, i+1+unit.value.INT);
322+
323+				if (extracted.size() == 2) {
324+					v1 = UnitLengthValue(n, units, extracted[0].start, extracted[0].end);
325+					v2 = UnitLengthValue(n, units, extracted[1].start, extracted[1].end);
326+
327+					value = std::fmodf(v1, v2);
328+				}
329+
330+				i += unit.value.INT;
331+				break;
332+			case UnitType::SIN:
333+				v1 = UnitLengthValue(n, units, i+1, i+1+unit.value.INT);
334+				value = std::sinf(v1);
335+				i += unit.value.INT;
336+				break;
337+			case UnitType::COS:
338+				v1 = UnitLengthValue(n, units, i+1, i+1+unit.value.INT);
339+				value = std::cosf(v1);
340+				i += unit.value.INT;
341+				break;
342+			case UnitType::TAN:
343+				v1 = UnitLengthValue(n, units, i+1, i+1+unit.value.INT);
344+				value = std::tanf(v1);
345+				i += unit.value.INT;
346+				break;
347+			case UnitType::ASIN:
348+				v1 = UnitLengthValue(n, units, i+1, i+1+unit.value.INT);
349+				value = std::asinf(v1);
350+				i += unit.value.INT;
351+				break;
352+			case UnitType::ACOS:
353+				v1 = UnitLengthValue(n, units, i+1, i+1+unit.value.INT);
354+				value = std::acosf(v1);
355+				i += unit.value.INT;
356+				break;
357+			case UnitType::ATAN:
358+				v1 = UnitLengthValue(n, units, i+1, i+1+unit.value.INT);
359+				value = std::atanf(v1);
360+				i += unit.value.INT;
361+				break;
362+			case UnitType::ATAN2:
363+				extracted = extractUnits(units, i+1, i+1+unit.value.INT);
364+
365+				if (extracted.size() == 2) {
366+					v1 = UnitLengthValue(n, units, extracted[0].start, extracted[0].end);
367+					v2 = UnitLengthValue(n, units, extracted[1].start, extracted[1].end);
368+
369+					value = std::atan2f(v1, v2);
370+				}
371+				i += unit.value.INT;
372+				break;
373+			case UnitType::POW:
374+				extracted = extractUnits(units, i+1, i+1+unit.value.INT);
375+
376+				if (extracted.size() == 2) {
377+					v1 = UnitLengthValue(n, units, extracted[0].start, extracted[0].end);
378+					v2 = UnitLengthValue(n, units, extracted[1].start, extracted[1].end);
379+
380+					value = std::powf(v1, v2);
381+				}
382+				i += unit.value.INT;
383+				break;
384+			case UnitType::SQRT:
385+				v1 = UnitLengthValue(n, units, i+1, i+1+unit.value.INT);
386+				value = std::sqrtf(v1);
387+				i += unit.value.INT;
388+				break;
389+			case UnitType::HYPOT:
390+				extracted = extractUnits(units, i+1, i+1+unit.value.INT);
391+
392+				v2 = 0;
393+				for (size_t j = 0; j < extracted.size(); j++) {
394+					v1 = UnitLengthValue(n, units, extracted[j].start, extracted[j].end);
395+
396+					v2 += std::powf(v1, 2);
397+				}
398+
399+				value = std::sqrtf(v2);
400+
401+				i += unit.value.INT;
402+				break;
403+			case UnitType::LOG:
404+				extracted = extractUnits(units, i+1, i+1+unit.value.INT);
405+
406+				if (extracted.size() == 2) {
407+					v1 = UnitLengthValue(n, units, extracted[0].start, extracted[0].end);
408+					v2 = UnitLengthValue(n, units, extracted[1].start, extracted[1].end);
409+
410+					value = std::logf(v1) / std::logf(v2);
411+				} else if (extracted.size() == 1) {
412+					v1 = UnitLengthValue(n, units, extracted[0].start, extracted[0].end);
413+
414+					value = std::logf(v1);
415+				}
416+
417+				i += unit.value.INT;
418+				break;
419+			case UnitType::EXP:
420+				v1 = UnitLengthValue(n, units, i+1, i+1+unit.value.INT);
421+				value = std::expf(v1);
422+				i += unit.value.INT;
423+				break;
424+			case UnitType::ABS:
425+				v1 = UnitLengthValue(n, units, i+1, i+1+unit.value.INT);
426+
427+				if (v1 < 0) v1 *= -1;
428+				value = v1;
429 				
430-			// Need to add variable injection
431+				i += unit.value.INT;
432+				break;
433+			case UnitType::SIGN:
434+				v1 = UnitLengthValue(n, units, i+1, i+1+unit.value.INT);
435+
436+				if (v1 > 0) {
437+					value = 1;
438+				} else if (v1 < 0) {
439+					value = -1;
440+				} else {
441+					value = 0;
442+				}
443+
444+				i += unit.value.INT;
445+				break;
446+			case UnitType::ATTR
447+
448+			// Need to add variable injection and group handling
449 		}
450 
451 		if (op != type) {
452diff --git a/src/parser.cc b/src/parser.cc
453index 2c156df..78fa6a0 100755
454--- a/src/parser.cc
455+++ b/src/parser.cc
456@@ -946,6 +946,10 @@ static const std::unordered_map<std::string_view, UnitType> kMatch{
457 	{"revert", UnitType::REVERT},
458 	{"revert-layer", UnitType::REVERT_LAYER},
459 	{"unset", UnitType::UNSET},
460+	{"nearest", UnitType::NEAREST},
461+	{"up", UnitType::UP},
462+	{"down", UnitType::DOWN},
463+	{"to-zero", UnitType::TO_ZERO},
464 };
465 
466 static const std::unordered_map<std::string_view, UnitType> kFunc{
467@@ -962,7 +966,7 @@ static const std::unordered_map<std::string_view, UnitType> kFunc{
468 	{"hypot", UnitType::HYPOT},
469 	{"log", UnitType::LOG},
470 	{"exp", UnitType::EXP},
471-	{"ABS", UnitType::ABS},
472+	{"abs", UnitType::ABS},
473 	{"sin", UnitType::SIN},
474 	{"cos", UnitType::COS},
475 	{"tan", UnitType::TAN},
476diff --git a/tests/windowunitlength.cc b/tests/windowunitlength.cc
477new file mode 100644
478index 0000000..495aa9b
479--- /dev/null
480+++ b/tests/windowunitlength.cc
481@@ -0,0 +1,201 @@
482+#include <catch_amalgamated.hpp>
483+#include "../include/grim.h"
484+#include "../include/parser.h" 
485+#include <string>
486+#include <sstream>
487+#include <vector>
488+#include <iostream>
489+
490+TEST_CASE("WindowUnitLengthValue") {
491+	std::string html = "<!DOCTYPE html>"
492+			"<html>"
493+				"<head>"
494+					"<title>This is a simple test</title>"
495+					"<link rel=\"stylesheet\" href=\'tests/style.css\'/>"
496+					"<style>h1 {background: red;}</style>"
497+				"</head>"
498+				"<body>"
499+					"<h1 id=\"h1Tag\">Header</h1>"
500+					"<p id=\"paragraph\" class=\"class1 class2\">This is some text for a simple test</p>"
501+					"<div contenteditable=\"true\">"
502+						"<p>this is a only child</p>"
503+					"</div>"
504+					"<input type=\"text\" checked required/>"
505+					"<ul>"
506+						"<li>one</li>"
507+						"<li>two</li>"
508+						"<li>three</li>"
509+						"<li>four</li>"
510+					"</ul>"
511+					"<input type=\"radio\" checked required/>"
512+					"<input type=\"text\" readonly/>"
513+					"<input type=\"text\" disabled/>"
514+					"<textarea></textarea>"
515+					"<textarea readonly></textarea>"
516+					"<textarea disabled></textarea>"
517+				"</body>"
518+			"</html>";
519+
520+
521+	std::stringstream ss(html);
522+
523+	Window window = createWindow();
524+	window.parseHTML(ss);
525+
526+	Node document = window.document;
527+
528+	document.cacheWidth(1000);
529+	document.cacheHeight(500);
530+	document.cacheEM(16);
531+
532+	SECTION("Absolute Physical Units") {
533+		// Testing conversions: CM (37.8), MM (3.78), IN (96)
534+		REQUIRE(UnitLengthValue(&document, getUnit("1cm"), 0, 1) == Catch::Approx(37.8f));
535+		REQUIRE(UnitLengthValue(&document, getUnit("10mm"), 0, 1) == Catch::Approx(37.8f));
536+		REQUIRE(UnitLengthValue(&document, getUnit("1in"), 0, 1) == Catch::Approx(96.0f));
537+		REQUIRE(UnitLengthValue(&document, getUnit("72pt"), 0, 1) == Catch::Approx(93.6f)); // 72 * 1.3
538+	}
539+
540+	SECTION("Viewport Units") {
541+		// VW = 1% of Width (1000), VH = 1% of Height (500)
542+		REQUIRE(UnitLengthValue(&document, getUnit("10vw"), 0, 1) == Catch::Approx(100.0f));
543+		REQUIRE(UnitLengthValue(&document, getUnit("50vh"), 0, 1) == Catch::Approx(250.0f));
544+
545+		// VMIN (smaller of 1000, 500) = 500. 10% of 500 = 50.
546+		// Note: Your VMIN code does unit.value * document (not /100), check if logic matches CSS spec
547+		REQUIRE(UnitLengthValue(&document, getUnit("0.1vmin"), 0, 1) == Catch::Approx(50.0f));
548+	}
549+
550+	SECTION("Basic Arithmetic (Sequential)") {
551+		// Tests the 'op' switching logic (ADD, SUB, MULT, DIV)
552+		std::vector<Unit> units = getUnit("10px + 20px * 2"); 
553+		// Note: Your function processes sequentially: (10 + 20) * 2 = 60
554+		// Standard CSS calc() has precedence, but your loop is linear.
555+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(60.0f));
556+	}
557+
558+	SECTION("Recursive Calc Blocks") {
559+		// Tests UnitType::CALC which uses unit.value.INT for jumping
560+		std::vector<Unit> units = getUnit("calc(10px + 5px)");
561+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(15.0f));
562+	}
563+
564+	SECTION("Min / Max Functions") {
565+		// min(100px, 200px, 50px)
566+		std::vector<Unit> unitsMin = getUnit("min(100px, 200px, 50px)");
567+		REQUIRE(UnitLengthValue(&document, unitsMin, 0, unitsMin.size()) == Catch::Approx(50.0f));
568+
569+		// max(100px, 200px, 50px)
570+		std::vector<Unit> unitsMax = getUnit("max(100px, 200px, 50px)");
571+		REQUIRE(UnitLengthValue(&document, unitsMax, 0, unitsMax.size()) == Catch::Approx(200.0f));
572+	}
573+
574+	SECTION("Clamp Function") {
575+		// clamp(MIN, VAL, MAX) -> clamp(100, 50, 200) should return 100
576+		std::vector<Unit> unitsClamp = getUnit("clamp(100px, 50px, 200px)");
577+		REQUIRE(UnitLengthValue(&document, unitsClamp, 0, unitsClamp.size()) == Catch::Approx(100.0f));
578+
579+		// clamp(100, 150, 200) should return 150
580+		std::vector<Unit> unitsMid = getUnit("clamp(100px, 150px, 200px)");
581+		REQUIRE(UnitLengthValue(&document, unitsMid, 0, unitsMid.size()) == Catch::Approx(150.0f));
582+	}
583+
584+	SECTION("Rounding Function") {
585+		// round(13px, 5px) -> Nearest 5 is 15px
586+		std::vector<Unit> unitsRound = getUnit("round(13px, 5px)");
587+		REQUIRE(UnitLengthValue(&document, unitsRound, 0, unitsRound.size()) == Catch::Approx(15.0f));
588+
589+		// round(down, 13px, 5px) -> 10px
590+		// Note: This requires your getUnit/Parser to support the Enum strategy correctly
591+		std::vector<Unit> unitsDown = getUnit("round(down, 13px, 5px)");
592+		REQUIRE(UnitLengthValue(&document, unitsDown, 0, unitsDown.size()) == Catch::Approx(10.0f));
593+	}
594+
595+	SECTION("Mod Function") {
596+		std::vector<Unit> units = getUnit("mod(18px, 4px)");
597+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(2.0f));
598+
599+		units = getUnit("mod(135deg, 90deg)");
600+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(45.0f));
601+	}
602+
603+	SECTION("Progress Function") {
604+		std::vector<Unit> units = getUnit("progress(300, 0, 1000)");
605+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(0.3f));
606+	}
607+
608+	SECTION("Rem Function") {
609+		std::vector<Unit> units = getUnit("rem(18px, 5px)");
610+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(3.0f));
611+	}
612+
613+	SECTION("Sin Function") {
614+		std::vector<Unit> units = getUnit("sin(45deg)");
615+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(0.8509035245341184f));
616+	}
617+
618+	SECTION("Cos Function") {
619+		std::vector<Unit> units = getUnit("cos(45deg)");
620+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(0.5253219888177297f));
621+	}
622+
623+	SECTION("Tan Function") {
624+		std::vector<Unit> units = getUnit("tan(45deg)");
625+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(1.6197751905438615f));
626+	}
627+	SECTION("ASin Function") {
628+		std::vector<Unit> units = getUnit("asin(-0.2deg)");
629+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(-0.2013579207903308f));
630+	}
631+
632+	SECTION("ACos Function") {
633+		std::vector<Unit> units = getUnit("acos(-0.2deg)");
634+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(1.7721542475852274f));
635+	}
636+
637+	SECTION("ATan Function") {
638+		std::vector<Unit> units = getUnit("atan(-0.2deg)");
639+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(-0.19739555984988078f));
640+	}
641+
642+	SECTION("ATan2 Function") {
643+		std::vector<Unit> units = getUnit("atan2(3, 2)");
644+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(0.982793723247329f));
645+	}
646+
647+	SECTION("Pow Function") {
648+		std::vector<Unit> units = getUnit("pow(3, 2)");
649+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(9.0f));
650+	}
651+	
652+	SECTION("Sqrt Function") {
653+		std::vector<Unit> units = getUnit("sqrt(3)");
654+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(1.7320508075688772f));
655+	}
656+
657+	SECTION("Hypot Function") {
658+		std::vector<Unit> units = getUnit("hypot(1em, 10px)");
659+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(18.867962264113206f));
660+
661+		units = getUnit("hypot(3px, 4px, 5px)");
662+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(7.0710678118654755f));
663+	}
664+
665+	SECTION("Exp Function") {
666+		std::vector<Unit> units = getUnit("exp(-1)");
667+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(0.367879441171442f));
668+
669+		units = getUnit("calc(100px * exp(-1))");
670+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(36.787944794f));
671+	}
672+
673+	SECTION("Abs Function") {
674+		std::vector<Unit> units = getUnit("abs(-3)");
675+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(3.0f));
676+	}
677+
678+	SECTION("Sign Function") {
679+		std::vector<Unit> units = getUnit("sign(-3)");
680+		REQUIRE(UnitLengthValue(&document, units, 0, units.size()) == Catch::Approx(-1.0f));
681+	}
682+};