1use std::cell::RefCell;
2
3use cssparser::{Parser, Token, match_ignore_ascii_case};
4
5use crate::style::{
6 Length, ONE_CM_IN_PX, ONE_IN_PX, ONE_MM_IN_PX, ONE_PC_IN_PX, ONE_PT_IN_PX, ONE_Q_IN_PX,
7 ParseResult, SizingContext, length_from_dimension_unit, unexpected_token,
8};
9
10#[derive(Default)]
11pub struct CalcArena {
12 linear_values: RefCell<Vec<CalcLinear>>,
13}
14
15impl CalcArena {
16 pub(crate) fn register_linear(&self, linear: CalcLinear) -> *const () {
17 let mut linear_values = self.linear_values.borrow_mut();
18
19 linear_values.push(linear);
20 encode_linear_id(linear_values.len())
21 }
22
23 pub fn resolve_calc_value(&self, val: *const (), basis: f32) -> f32 {
24 let Some(id) = decode_linear_id(val) else {
25 return 0.0;
26 };
27
28 let linear_values = self.linear_values.borrow();
29 linear_values
30 .get(id - 1)
31 .map(|linear| linear.resolve(basis))
32 .unwrap_or(0.0)
33 }
34}
35
36fn encode_linear_id(id: usize) -> *const () {
37 (id << 3) as *const ()
39}
40
41fn decode_linear_id(ptr: *const ()) -> Option<usize> {
42 let raw = ptr as usize;
43 if raw & 0b111 != 0 {
46 return None;
47 }
48 let id = raw >> 3;
49 (id != 0).then_some(id)
50}
51
52#[derive(Debug, Clone, Copy, PartialEq)]
53pub struct CalcLinear {
55 pub(crate) px: f32,
56 pub(crate) percent: f32,
57}
58
59impl CalcLinear {
60 pub(crate) fn resolve(self, basis: f32) -> f32 {
61 self.px + self.percent * basis
62 }
63
64 pub fn components(self) -> (f32, f32) {
65 (self.px, self.percent)
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Default)]
70pub struct CalcFormula {
72 pub(crate) px: f32,
73 pub(crate) percent: f32,
74 pub(crate) rem: f32,
75 pub(crate) em: f32,
76 pub(crate) lh: f32,
77 pub(crate) rlh: f32,
78 pub(crate) vh: f32,
79 pub(crate) vw: f32,
80 pub(crate) cqh: f32,
81 pub(crate) cqw: f32,
82 pub(crate) cqmin: f32,
83 pub(crate) cqmax: f32,
84 pub(crate) vmin: f32,
85 pub(crate) vmax: f32,
86 pub(crate) cm: f32,
87 pub(crate) mm: f32,
88 pub(crate) inch: f32,
89 pub(crate) q: f32,
90 pub(crate) pt: f32,
91 pub(crate) pc: f32,
92}
93
94macro_rules! for_each_unit {
96 ($callback:ident) => {
97 $callback!(
98 px, percent, rem, em, lh, rlh, vh, vw, cqh, cqw, cqmin, cqmax, vmin, vmax, cm, mm, inch, q,
99 pt, pc
100 );
101 };
102}
103
104macro_rules! calc_constructors {
105 ($($field:ident),+) => {
106 $(
107 fn $field(value: f32) -> Self {
108 Self { $field: value, ..Default::default() }
109 }
110 )+
111 };
112}
113
114macro_rules! calc_neg {
115 ($($field:ident),+) => {
116 pub(crate) fn neg(self) -> Self {
117 Self { $($field: -self.$field),+ }
118 }
119 };
120}
121
122macro_rules! calc_add {
123 ($($field:ident),+) => {
124 fn add(self, rhs: Self) -> Self {
125 Self { $($field: self.$field + rhs.$field),+ }
126 }
127 };
128}
129
130macro_rules! calc_sub {
131 ($($field:ident),+) => {
132 fn sub(self, rhs: Self) -> Self {
133 Self { $($field: self.$field - rhs.$field),+ }
134 }
135 };
136}
137
138macro_rules! calc_scale {
139 ($($field:ident),+) => {
140 fn scale(self, factor: f32) -> Self {
141 Self { $($field: Self::scale_component(self.$field, factor)),+ }
142 }
143 };
144}
145
146impl CalcFormula {
147 fn scale_component(value: f32, factor: f32) -> f32 {
148 if value == 0.0 { 0.0 } else { value * factor }
149 }
150
151 for_each_unit!(calc_constructors);
152 for_each_unit!(calc_neg);
153 for_each_unit!(calc_add);
154 for_each_unit!(calc_sub);
155 for_each_unit!(calc_scale);
156}
157
158impl CalcFormula {
159 pub fn resolve(self, sizing: &SizingContext) -> CalcLinear {
160 let viewport_width = sizing.viewport.size.width.unwrap_or_default() as f32;
161 let viewport_height = sizing.viewport.size.height.unwrap_or_default() as f32;
162 let viewport_min = viewport_width.min(viewport_height);
163 let viewport_max = viewport_width.max(viewport_height);
164 let container_width = sizing.query_container_width();
165 let container_height = sizing.query_container_height();
166 let container_min = container_width.min(container_height);
167 let container_max = container_width.max(container_height);
168
169 CalcLinear {
170 px: self.px * sizing.viewport.device_pixel_ratio
171 + self.rem * sizing.rem_basis()
172 + self.em * sizing.font_size
173 + self.lh * sizing.line_height
174 + self.rlh * sizing.root_line_height_basis()
175 + self.vh * viewport_height / 100.0
176 + self.vw * viewport_width / 100.0
177 + self.cqh * container_height / 100.0
178 + self.cqw * container_width / 100.0
179 + self.cqmin * container_min / 100.0
180 + self.cqmax * container_max / 100.0
181 + self.vmin * viewport_min / 100.0
182 + self.vmax * viewport_max / 100.0
183 + self.cm * ONE_CM_IN_PX * sizing.viewport.device_pixel_ratio
184 + self.mm * ONE_MM_IN_PX * sizing.viewport.device_pixel_ratio
185 + self.inch * ONE_IN_PX * sizing.viewport.device_pixel_ratio
186 + self.q * ONE_Q_IN_PX * sizing.viewport.device_pixel_ratio
187 + self.pt * ONE_PT_IN_PX * sizing.viewport.device_pixel_ratio
188 + self.pc * ONE_PC_IN_PX * sizing.viewport.device_pixel_ratio,
189 percent: self.percent,
190 }
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq)]
195pub(crate) enum CalcValue {
196 Number(f32),
197 Formula(CalcFormula),
198}
199
200pub(crate) fn parse_calc_sum<'i>(input: &mut Parser<'i, '_>) -> ParseResult<'i, CalcValue> {
201 let mut value = parse_calc_product(input)?;
202
203 loop {
204 if input.try_parse(|parser| parser.expect_delim('+')).is_ok() {
205 let rhs = parse_calc_product(input)?;
206 value = match (value, rhs) {
207 (CalcValue::Number(lhs), CalcValue::Number(rhs)) => CalcValue::Number(lhs + rhs),
208 (CalcValue::Formula(lhs), CalcValue::Formula(rhs)) => CalcValue::Formula(lhs.add(rhs)),
209 _ => {
210 return Err(unexpected_token!(
211 Length,
212 input.current_source_location(),
213 &Token::Delim('+'),
214 ));
215 }
216 };
217 continue;
218 }
219
220 if input.try_parse(|parser| parser.expect_delim('-')).is_ok() {
221 let rhs = parse_calc_product(input)?;
222 value = match (value, rhs) {
223 (CalcValue::Number(lhs), CalcValue::Number(rhs)) => CalcValue::Number(lhs - rhs),
224 (CalcValue::Formula(lhs), CalcValue::Formula(rhs)) => CalcValue::Formula(lhs.sub(rhs)),
225 _ => {
226 return Err(unexpected_token!(
227 Length,
228 input.current_source_location(),
229 &Token::Delim('-'),
230 ));
231 }
232 };
233 continue;
234 }
235
236 break;
237 }
238
239 Ok(value)
240}
241
242pub fn parse_calc_number_expression<'i>(input: &mut Parser<'i, '_>) -> ParseResult<'i, f32> {
243 let location = input.current_source_location();
244 let token = input.next()?.clone();
245
246 match &token {
247 Token::Function(function) if function.eq_ignore_ascii_case("calc") => {
248 match input.parse_nested_block(parse_calc_sum)? {
249 CalcValue::Number(value) => Ok(value),
250 _ => Err(location.new_unexpected_token_error(token.clone())),
251 }
252 }
253 _ => Err(location.new_unexpected_token_error(token.clone())),
254 }
255}
256
257fn parse_calc_product<'i>(input: &mut Parser<'i, '_>) -> ParseResult<'i, CalcValue> {
258 let mut value = parse_calc_factor(input)?;
259
260 loop {
261 if input.try_parse(|parser| parser.expect_delim('*')).is_ok() {
262 let rhs = parse_calc_factor(input)?;
263 value = match (value, rhs) {
264 (CalcValue::Formula(lhs), CalcValue::Number(rhs)) => CalcValue::Formula(lhs.scale(rhs)),
265 (CalcValue::Number(lhs), CalcValue::Formula(rhs)) => CalcValue::Formula(rhs.scale(lhs)),
266 (CalcValue::Number(lhs), CalcValue::Number(rhs)) => CalcValue::Number(lhs * rhs),
267 _ => {
268 return Err(unexpected_token!(
269 Length,
270 input.current_source_location(),
271 &Token::Delim('*'),
272 ));
273 }
274 };
275 continue;
276 }
277
278 if input.try_parse(|parser| parser.expect_delim('/')).is_ok() {
279 let rhs = parse_calc_factor(input)?;
280 value = match (value, rhs) {
281 (_, CalcValue::Number(0.0)) => {
282 return Err(unexpected_token!(
283 Length,
284 input.current_source_location(),
285 &Token::Delim('/'),
286 ));
287 }
288 (CalcValue::Formula(lhs), CalcValue::Number(rhs)) => {
289 CalcValue::Formula(lhs.scale(1.0 / rhs))
290 }
291 (CalcValue::Number(lhs), CalcValue::Number(rhs)) => CalcValue::Number(lhs / rhs),
292 _ => {
293 return Err(unexpected_token!(
294 Length,
295 input.current_source_location(),
296 &Token::Delim('/'),
297 ));
298 }
299 };
300 continue;
301 }
302
303 break;
304 }
305
306 Ok(value)
307}
308
309impl CalcFormula {
310 fn from_length<const DEFAULT_AUTO: bool>(length: Length<DEFAULT_AUTO>) -> Self {
312 match length {
313 Length::Px(v) => Self::px(v),
314 Length::Em(v) => Self::em(v),
315 Length::Rem(v) => Self::rem(v),
316 Length::Lh(v) => Self::lh(v),
317 Length::Rlh(v) => Self::rlh(v),
318 Length::Vw(v) => Self::vw(v),
319 Length::CqW(v) => Self::cqw(v),
320 Length::Vh(v) => Self::vh(v),
321 Length::CqH(v) => Self::cqh(v),
322 Length::VMin(v) => Self::vmin(v),
323 Length::CqMin(v) => Self::cqmin(v),
324 Length::VMax(v) => Self::vmax(v),
325 Length::CqMax(v) => Self::cqmax(v),
326 Length::Cm(v) => Self::cm(v),
327 Length::Mm(v) => Self::mm(v),
328 Length::In(v) => Self::inch(v),
329 Length::Q(v) => Self::q(v),
330 Length::Pt(v) => Self::pt(v),
331 Length::Pc(v) => Self::pc(v),
332 _ => Self::default(),
333 }
334 }
335}
336
337fn parse_calc_factor<'i>(input: &mut Parser<'i, '_>) -> ParseResult<'i, CalcValue> {
338 if input.try_parse(|parser| parser.expect_delim('+')).is_ok() {
339 return parse_calc_factor(input);
340 }
341
342 if input.try_parse(|parser| parser.expect_delim('-')).is_ok() {
343 return Ok(match parse_calc_factor(input)? {
344 CalcValue::Number(value) => CalcValue::Number(-value),
345 CalcValue::Formula(formula) => CalcValue::Formula(formula.neg()),
346 });
347 }
348
349 let location = input.current_source_location();
350 let token = input.next()?;
351
352 match token {
353 Token::Number { value, .. } => Ok(CalcValue::Number(*value)),
354 Token::Percentage { unit_value, .. } => {
355 Ok(CalcValue::Formula(CalcFormula::percent(*unit_value)))
356 }
357 Token::Dimension { value, unit, .. } => {
358 match length_from_dimension_unit::<true>(unit.as_ref(), *value) {
359 Some(length) => Ok(CalcValue::Formula(CalcFormula::from_length(length))),
360 None => Err(unexpected_token!(Length, location, token)),
361 }
362 }
363 Token::Function(name) if name.eq_ignore_ascii_case("calc") => {
364 input.parse_nested_block(parse_calc_sum)
365 }
366 Token::Ident(ident) => match_ignore_ascii_case! {ident.as_ref(),
367 "e" => Ok(CalcValue::Number(std::f32::consts::E)),
368 "pi" => Ok(CalcValue::Number(std::f32::consts::PI)),
369 "infinity" => Ok(CalcValue::Number(f32::INFINITY)),
370 "-infinity" => Ok(CalcValue::Number(f32::NEG_INFINITY)),
371 "nan" => Ok(CalcValue::Number(f32::NAN)),
372 _ => Err(unexpected_token!(Length, location, token)),
373 },
374 _ => Err(unexpected_token!(Length, location, token)),
375 }
376}