1pub const ROOT_FONT_SIZE_PX: f32 = 16.0;
10
11pub fn parse_font_size_with_parent(s: &str, parent_font_px: f32) -> Option<u32> {
23 let t = s.trim();
24 if t.is_empty() {
25 return None;
26 }
27 let parent = if parent_font_px > 0.0 { parent_font_px } else { ROOT_FONT_SIZE_PX };
28
29 let (num_str, scale) = if let Some(n) = t.strip_suffix("rem") {
30 (n, ROOT_FONT_SIZE_PX)
31 } else if let Some(n) = t.strip_suffix("em") {
32 (n, parent)
33 } else if let Some(n) = t.strip_suffix('%') {
34 (n, parent / 100.0)
35 } else if let Some(n) = t.strip_suffix("px") {
36 (n, 1.0)
37 } else if let Some(n) = t.strip_suffix("pt") {
38 (n, 4.0 / 3.0)
39 } else {
40 (t, 1.0)
41 };
42
43 let val = num_str.trim().parse::<f32>().ok()?;
44 if !val.is_finite() || val < 0.0 {
45 return None;
46 }
47 Some((val * scale) as u32)
48}
49
50pub fn parse_font_size(s: &str) -> Option<u32> {
51 let t = s.trim();
52 if t.is_empty() {
53 return None;
54 }
55
56 let (num_str, scale) = if let Some(n) = t.strip_suffix("rem") {
57 (n, ROOT_FONT_SIZE_PX)
58 } else if let Some(n) = t.strip_suffix("em") {
59 (n, ROOT_FONT_SIZE_PX)
60 } else if let Some(n) = t.strip_suffix("px") {
61 (n, 1.0)
62 } else if let Some(n) = t.strip_suffix("pt") {
63 (n, 4.0 / 3.0)
64 } else {
65 (t, 1.0)
66 };
67
68 let val = num_str.trim().parse::<f32>().ok()?;
69 if !val.is_finite() || val < 0.0 {
70 return None;
71 }
72 Some((val * scale) as u32)
73}
74
75pub fn resolve_text_indent(s: &str, container_width: i32, font_size: u32) -> i32 {
78 let t = s.trim();
79 let px = if let Some(num) = t.strip_suffix("px") {
80 num.trim().parse::<f32>().ok()
81 } else if let Some(num) = t.strip_suffix('%') {
82 num.trim()
83 .parse::<f32>()
84 .ok()
85 .map(|v| v / 100.0 * container_width as f32)
86 } else if let Some(num) = t.strip_suffix("rem") {
87 num.trim().parse::<f32>().ok().map(|v| v * ROOT_FONT_SIZE_PX)
88 } else if let Some(num) = t.strip_suffix("em") {
89 num.trim().parse::<f32>().ok().map(|v| v * font_size as f32)
90 } else {
91 t.parse::<f32>().ok()
92 };
93 px.map(|f| f as i32).unwrap_or(0)
94}
95
96pub fn find_balanced_close(s: &str, open_idx: usize) -> Option<usize> {
106 let bytes = s.as_bytes();
107 if bytes.get(open_idx) != Some(&b'(') {
108 crate::error!(
109 "[CSS][PAREN] 開き括弧の位置が不正です: idx={} len={}",
110 open_idx,
111 s.len()
112 );
113 return None;
114 }
115 let mut depth = 0i32;
116 for (i, b) in bytes.iter().enumerate().skip(open_idx) {
117 match b {
118 b'(' => depth += 1,
119 b')' => {
120 depth -= 1;
121 if depth == 0 {
122 return Some(i);
123 }
124 }
125 _ => {}
126 }
127 }
128 None
130}
131
132pub fn split_top_level_commas(s: &str) -> alloc::vec::Vec<&str> {
141 let mut out = alloc::vec::Vec::new();
142 let mut depth = 0i32;
143 let mut quote: Option<u8> = None;
144 let mut start = 0usize;
145 for (i, b) in s.as_bytes().iter().enumerate() {
146 match (*b, quote) {
147 (q, Some(active)) if q == active => quote = None,
148 (_, Some(_)) => {}
149 (b'\'', None) | (b'"', None) => quote = Some(*b),
150 (b'(', None) => depth += 1,
151 (b')', None) => depth -= 1,
152 (b',', None) if depth == 0 => {
153 out.push(s.get(start..i).unwrap_or(""));
154 start = i + 1;
155 }
156 _ => {}
157 }
158 }
159 out.push(s.get(start..).unwrap_or(""));
160 out
161}
162
163pub fn resolve_em_lengths(value: &str, font_px: f32) -> alloc::string::String {
178 let chars: alloc::vec::Vec<char> = value.chars().collect();
179 let n = chars.len();
180 let mut out = alloc::string::String::with_capacity(value.len());
181 let mut i = 0;
182 while i < n {
183 let c = chars[i];
184 let at_boundary = i == 0 || {
185 let p = chars[i - 1];
186 !(p.is_ascii_alphanumeric() || p == '.' || p == '_')
187 };
188 let sign = c == '-' || c == '+';
189 let starts_number = c.is_ascii_digit()
190 || c == '.'
191 || (sign && i + 1 < n && (chars[i + 1].is_ascii_digit() || chars[i + 1] == '.'));
192 if at_boundary && starts_number {
193 let start = i;
194 let mut j = if sign { i + 1 } else { i };
195 let mut saw_digit = false;
196 while j < n && (chars[j].is_ascii_digit() || chars[j] == '.') {
197 saw_digit |= chars[j].is_ascii_digit();
198 j += 1;
199 }
200 let is_em = saw_digit
201 && chars.get(j).is_some_and(|u| u.eq_ignore_ascii_case(&'e'))
202 && chars.get(j + 1).is_some_and(|u| u.eq_ignore_ascii_case(&'m'))
203 && chars.get(j + 2).is_none_or(|u| !u.is_ascii_alphanumeric());
204 let num: alloc::string::String = chars[start..j].iter().collect();
205 if is_em {
206 if let Ok(v) = num.parse::<f32>() {
207 let px = libm::roundf(v * font_px) as i64;
208 if c == '+' {
209 out.push('+');
210 }
211 out.push_str(&alloc::format!("{}px", px));
212 i = j + 2;
213 continue;
214 }
215 }
216 out.push_str(&num);
217 i = j;
218 continue;
219 }
220 out.push(c);
221 i += 1;
222 }
223 out
224}
225
226pub fn parse_angle_to_deg(s: &str) -> Option<f32> {
234 let t = s.trim();
235 if t.is_empty() {
236 return None;
237 }
238 let lower = t.to_ascii_lowercase();
239 let (num_str, scale) = if let Some(n) = lower.strip_suffix("deg") {
240 (n, 1.0)
241 } else if let Some(n) = lower.strip_suffix("grad") {
242 (n, 0.9)
243 } else if let Some(n) = lower.strip_suffix("turn") {
244 (n, 360.0)
245 } else if let Some(n) = lower.strip_suffix("rad") {
246 (n, 57.295_779_5)
247 } else {
248 (lower.as_str(), 1.0)
249 };
250 let val = num_str.trim().parse::<f32>().ok()?;
251 if !val.is_finite() {
252 return None;
253 }
254 Some(val * scale)
255}
256
257pub fn parse_angle_to_rad(s: &str) -> Option<f32> {
259 parse_angle_to_deg(s).map(|deg| deg * (core::f32::consts::PI / 180.0))
260}
261
262pub fn resolve_translate_component(s: &str, ref_size: f32, font_px: f32) -> i32 {
269 let t = s.trim();
270 if t.is_empty() {
271 return 0;
272 }
273 let font = if font_px > 0.0 { font_px } else { ROOT_FONT_SIZE_PX };
274 let px = if let Some(n) = t.strip_suffix("px") {
275 n.trim().parse::<f32>().ok()
276 } else if let Some(n) = t.strip_suffix('%') {
277 n.trim().parse::<f32>().ok().map(|v| v / 100.0 * ref_size)
278 } else if let Some(n) = t.strip_suffix("rem") {
279 n.trim().parse::<f32>().ok().map(|v| v * ROOT_FONT_SIZE_PX)
280 } else if let Some(n) = t.strip_suffix("em") {
281 n.trim().parse::<f32>().ok().map(|v| v * font)
282 } else {
283 t.parse::<f32>().ok()
284 };
285 px.map(|v| libm::roundf(v) as i32).unwrap_or(0)
286}
287
288pub fn parse_transform_with_size(
294 s: Option<&str>,
295 elem_w: f32,
296 elem_h: f32,
297 font_px: f32,
298) -> (i32, i32, f32, f32, f32, f32, f32) {
299 let s = match s {
300 Some(v) if v.trim() != "none" => v.trim(),
301 _ => return (0, 0, 1.0, 1.0, 0.0, 0.0, 0.0),
302 };
303 let mut tx = 0i32;
304 let mut ty = 0i32;
305 let mut scale_x = 1.0f32;
306 let mut scale_y = 1.0f32;
307 let mut rotate = 0.0f32;
308 let mut skew_x = 0.0f32;
309 let mut skew_y = 0.0f32;
310
311 let mut rest = s;
312 while !rest.is_empty() {
313 rest = rest.trim_start();
314 if let Some(after) = rest.strip_prefix("translateX(") {
315 if let Some((inside, after_rest)) = after.split_once(')') {
316 tx += resolve_translate_component(inside.trim(), elem_w, font_px);
317 rest = after_rest;
318 } else {
319 break;
320 }
321 } else if let Some(after) = rest.strip_prefix("translateY(") {
322 if let Some((inside, after_rest)) = after.split_once(')') {
323 ty += resolve_translate_component(inside.trim(), elem_h, font_px);
324 rest = after_rest;
325 } else {
326 break;
327 }
328 } else if let Some(after) = rest.strip_prefix("translate(") {
329 if let Some((args, after_rest)) = after.split_once(')') {
330 let mut it = args.splitn(2, ',');
331 let x_str = it.next().unwrap_or("").trim();
332 let y_str = it.next().unwrap_or("0").trim();
333 tx += resolve_translate_component(x_str, elem_w, font_px);
334 ty += resolve_translate_component(y_str, elem_h, font_px);
335 rest = after_rest;
336 } else {
337 break;
338 }
339 } else if let Some(after) = rest.strip_prefix("translate3d(") {
340 if let Some((args, after_rest)) = after.split_once(')') {
341 let mut it = args.splitn(3, ',');
342 let x_str = it.next().unwrap_or("").trim();
343 let y_str = it.next().unwrap_or("0").trim();
344 tx += resolve_translate_component(x_str, elem_w, font_px);
345 ty += resolve_translate_component(y_str, elem_h, font_px);
346 rest = after_rest;
347 } else {
348 break;
349 }
350 } else if let Some(after) = rest.strip_prefix("scale(") {
351 if let Some((inside, after_rest)) = after.split_once(')') {
352 let mut it = inside.split(',');
353 let vx = it.next().unwrap_or("1").trim().parse::<f32>().unwrap_or(1.0);
354 let vy = it
355 .next()
356 .map(|v| v.trim().parse::<f32>().unwrap_or(vx))
357 .unwrap_or(vx);
358 scale_x *= vx;
359 scale_y *= vy;
360 rest = after_rest;
361 } else {
362 break;
363 }
364 } else if let Some(after) = rest.strip_prefix("scaleX(") {
365 if let Some((inside, after_rest)) = after.split_once(')') {
366 scale_x *= inside.trim().parse::<f32>().unwrap_or(1.0);
367 rest = after_rest;
368 } else {
369 break;
370 }
371 } else if let Some(after) = rest.strip_prefix("scaleY(") {
372 if let Some((inside, after_rest)) = after.split_once(')') {
373 scale_y *= inside.trim().parse::<f32>().unwrap_or(1.0);
374 rest = after_rest;
375 } else {
376 break;
377 }
378 } else if let Some(after) = rest.strip_prefix("rotate(") {
379 if let Some((inside, after_rest)) = after.split_once(')') {
380 if let Some(deg) = parse_angle_to_deg(inside.trim()) {
381 rotate += deg;
382 }
383 rest = after_rest;
384 } else {
385 break;
386 }
387 } else if let Some(after) = rest.strip_prefix("matrix(") {
388 if let Some((args_str, after_rest)) = after.split_once(')') {
389 let args: alloc::vec::Vec<&str> = args_str.split(',').collect();
390 tx += args
391 .get(4)
392 .and_then(|v| v.trim().parse::<f32>().ok())
393 .map(|v| v as i32)
394 .unwrap_or(0);
395 ty += args
396 .get(5)
397 .and_then(|v| v.trim().parse::<f32>().ok())
398 .map(|v| v as i32)
399 .unwrap_or(0);
400 rest = after_rest;
401 } else {
402 break;
403 }
404 } else if let Some(after) = rest.strip_prefix("skewX(") {
405 if let Some((inside, after_rest)) = after.split_once(')') {
406 if let Some(deg) = parse_angle_to_deg(inside.trim()) {
407 skew_x += deg;
408 }
409 rest = after_rest;
410 } else {
411 break;
412 }
413 } else if let Some(after) = rest.strip_prefix("skewY(") {
414 if let Some((inside, after_rest)) = after.split_once(')') {
415 if let Some(deg) = parse_angle_to_deg(inside.trim()) {
416 skew_y += deg;
417 }
418 rest = after_rest;
419 } else {
420 break;
421 }
422 } else if let Some(after) = rest.strip_prefix("skew(") {
423 if let Some((args, after_rest)) = after.split_once(')') {
424 let mut it = args.splitn(2, ',');
425 let ax_str = it.next().unwrap_or("").trim();
426 if let Some(deg) = parse_angle_to_deg(ax_str) {
427 skew_x += deg;
428 }
429 if let Some(ay_str) = it.next() {
430 if let Some(deg) = parse_angle_to_deg(ay_str.trim()) {
431 skew_y += deg;
432 }
433 }
434 rest = after_rest;
435 } else {
436 break;
437 }
438 } else {
439 if let Some((_, after_rest)) = rest.split_once(')') {
441 rest = after_rest;
442 } else {
443 break;
444 }
445 }
446 }
447
448 (tx, ty, scale_x, scale_y, rotate, skew_x, skew_y)
449}
450