1use super::*;
4
5pub(crate) static IMAGE_SIZE_CACHE: Mutex<Option<BTreeMap<String, (i32, i32)>>> = Mutex::new(None);
6
7pub fn register_image_natural_size(src: &str, w: i32, h: i32) {
17 let mut cache = IMAGE_SIZE_CACHE.lock();
18 if cache.is_none() {
19 *cache = Some(BTreeMap::new());
20 }
21 if let Some(ref mut c) = *cache {
22 c.insert(alloc::string::String::from(src), (w, h));
23 }
24}
25
26#[derive(Debug, Clone, Default)]
27pub struct Rect {
28 pub x: i32,
29 pub y: i32,
30 pub width: i32,
31 pub height: i32,
32}
33
34#[derive(Debug, Clone, Default)]
35pub struct EdgeSizes {
36 pub left: i32,
37 pub right: i32,
38 pub top: i32,
39 pub bottom: i32,
40}
41
42#[derive(Debug, Clone, Default)]
43pub struct Dimensions {
44 pub content: Rect,
45 pub padding: EdgeSizes,
46 pub border: EdgeSizes,
47 pub margin: EdgeSizes,
48}
49
50impl Dimensions {
51 pub fn margin_box(&self) -> Rect {
52 Rect {
53 x: self.content.x - self.padding.left - self.border.left - self.margin.left,
54 y: self.content.y - self.padding.top - self.border.top - self.margin.top,
55 width: self.content.width
56 + self.padding.left
57 + self.padding.right
58 + self.border.left
59 + self.border.right
60 + self.margin.left
61 + self.margin.right,
62 height: self.content.height
63 + self.padding.top
64 + self.padding.bottom
65 + self.border.top
66 + self.border.bottom
67 + self.margin.top
68 + self.margin.bottom,
69 }
70 }
71}
72
73pub struct LayoutBox<'a> {
74 pub dimensions: Dimensions,
75 pub box_type: BoxType<'a>,
76 pub children: Vec<LayoutBox<'a>>,
77}
78
79pub enum BoxType<'a> {
80 BlockNode(&'a StyledNode<'a>),
81 InlineNode(&'a StyledNode<'a>),
82 InlineBlockNode(&'a StyledNode<'a>),
83 FlexNode(&'a StyledNode<'a>),
84 GridNode(&'a StyledNode<'a>),
85 TableNode(&'a StyledNode<'a>),
86 TableRowGroupNode(&'a StyledNode<'a>),
87 TableRowNode(&'a StyledNode<'a>),
88 TableCellNode(&'a StyledNode<'a>),
89 AnonymousBlock,
90}
91
92pub(crate) fn default_display_for(tag_name: &str) -> &'static str {
95 match tag_name {
96 "head" | "title" | "meta" | "link" | "style" | "script" => "none",
97 "html" | "body" | "div" | "p" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "ul" | "ol"
98 | "li" | "form" | "center" | "header" | "footer" | "main" | "section" | "nav" | "hr"
99 | "caption" | "details" | "summary" => "block",
100 "table" => "table",
101 "thead" | "tbody" | "tfoot" => "table-row-group",
102 "tr" => "table-row",
103 "td" | "th" => "table-cell",
104 _ => "inline",
105 }
106}
107
108pub(crate) fn cell_spans(cell: &LayoutBox) -> (usize, usize) {
110 if let BoxType::TableCellNode(styled) = &cell.box_type {
111 if let NodeType::Element { attributes, .. } = &styled.node.node_type {
112 let cs = attributes
113 .get("colspan")
114 .and_then(|v| v.trim().parse::<usize>().ok())
115 .unwrap_or(1)
116 .clamp(1, 256);
117 let rs = attributes
118 .get("rowspan")
119 .and_then(|v| v.trim().parse::<usize>().ok())
120 .unwrap_or(1)
121 .clamp(1, 1024);
122 return (cs, rs);
123 }
124 }
125 (1, 1)
126}
127
128pub(crate) fn box_tag_name<'a>(b: &LayoutBox<'a>) -> Option<&'a str> {
130 let styled = match &b.box_type {
131 BoxType::BlockNode(s)
132 | BoxType::InlineNode(s)
133 | BoxType::InlineBlockNode(s)
134 | BoxType::FlexNode(s)
135 | BoxType::GridNode(s)
136 | BoxType::TableNode(s)
137 | BoxType::TableRowGroupNode(s)
138 | BoxType::TableRowNode(s)
139 | BoxType::TableCellNode(s) => *s,
140 BoxType::AnonymousBlock => return None,
141 };
142 match &styled.node.node_type {
143 NodeType::Element { tag_name, .. } => Some(tag_name.as_str()),
144 NodeType::Text(_) => None,
145 }
146}
147
148pub(crate) fn box_style_value<'a>(b: &LayoutBox<'a>, prop: &str) -> Option<String> {
150 let styled = match &b.box_type {
151 BoxType::BlockNode(s)
152 | BoxType::InlineNode(s)
153 | BoxType::InlineBlockNode(s)
154 | BoxType::FlexNode(s)
155 | BoxType::GridNode(s)
156 | BoxType::TableNode(s)
157 | BoxType::TableRowGroupNode(s)
158 | BoxType::TableRowNode(s)
159 | BoxType::TableCellNode(s) => *s,
160 BoxType::AnonymousBlock => return None,
161 };
162 styled.value(prop)
163}
164
165pub(crate) fn box_has_explicit(b: &LayoutBox, prop: &str) -> bool {
167 box_style_value(b, prop)
168 .map(|v| {
169 let t = v.trim().to_lowercase();
170 !t.is_empty() && t != "auto"
171 })
172 .unwrap_or(false)
173}
174
175pub(crate) fn box_flex_grow(b: &LayoutBox) -> i32 {
177 if let Some(g) = box_style_value(b, "flex-grow") {
178 return g.trim().parse::<i32>().unwrap_or(0).max(0);
179 }
180 if let Some(f) = box_style_value(b, "flex") {
181 let t = f.trim().to_lowercase();
182 if t == "none" || t == "initial" {
183 return 0;
184 }
185 if t == "auto" {
186 return 1; }
188 if let Some(first) = t.split_whitespace().next() {
189 return first.parse::<i32>().unwrap_or(0).max(0);
190 }
191 }
192 0
193}
194
195pub(crate) fn is_flex_length_token(tok: &str) -> bool {
205 for suffix in ["px", "%", "rem", "em", "vh", "vw"] {
206 if let Some(n) = tok.strip_suffix(suffix) {
207 if n.trim().parse::<f32>().is_ok() {
208 return true;
209 }
210 }
211 }
212 false
213}
214
215pub(crate) fn box_flex_shrink(b: &LayoutBox) -> i32 {
216 if let Some(s) = box_style_value(b, "flex-shrink") {
217 return s.trim().parse::<i32>().unwrap_or(1).max(0);
218 }
219 if let Some(f) = box_style_value(b, "flex") {
220 let t = f.trim().to_lowercase();
221 if t == "none" {
222 return 0; }
224 if t == "initial" {
225 return 1; }
227 if t == "auto" {
228 return 1; }
230 let parts: Vec<&str> = t.split_whitespace().collect();
232 if let Some(second) = parts.get(1) {
233 if !is_flex_length_token(second) && *second != "auto" {
235 return second.parse::<i32>().unwrap_or(1).max(0);
236 }
237 }
238 return 1;
240 }
241 1
242}
243
244pub(crate) fn box_order(b: &LayoutBox) -> i32 {
246 box_style_value(b, "order")
247 .and_then(|v| v.trim().parse::<i32>().ok())
248 .unwrap_or(0)
249}
250
251pub(crate) fn box_flex_basis(b: &LayoutBox, main_size: i32) -> Option<i32> {
255 if let Some(v) = box_style_value(b, "flex-basis") {
257 let t = v.trim().to_lowercase();
258 if t.is_empty() || t == "auto" || t == "content" {
259 return None;
260 }
261 return resolve_basis_len(&t, main_size);
262 }
263 if let Some(f) = box_style_value(b, "flex") {
265 let t = f.trim().to_lowercase();
266 if t == "none" || t == "auto" || t == "initial" {
267 return None; }
269 for tok in t.split_whitespace() {
271 if is_flex_length_token(tok) {
272 return resolve_basis_len(tok, main_size);
273 }
274 }
275 if t.split_whitespace().count() == 1 && t.parse::<i32>().is_ok() {
277 return Some(0);
278 }
279 }
280 None
281}
282
283pub(crate) fn resolve_basis_len(tok: &str, main_size: i32) -> Option<i32> {
285 if let Some(p) = tok.strip_suffix('%') {
286 return p
287 .trim()
288 .parse::<f32>()
289 .ok()
290 .map(|pct| round_f32_to_i32(main_size as f32 * pct / 100.0).max(0));
291 }
292 parse_px_like(tok).map(|v| v.max(0))
293}
294
295pub(crate) fn box_align_self(b: &LayoutBox) -> Option<String> {
297 box_style_value(b, "align-self").and_then(|v| {
298 let t = v.trim().to_lowercase();
299 if t.is_empty() || t == "auto" {
300 None
301 } else {
302 Some(t)
303 }
304 })
305}
306
307pub(crate) fn grid_item_align(b: &LayoutBox, self_prop: &str, items_default: &str) -> &'static str {
312 let raw = match box_style_value(b, self_prop) {
313 Some(v) => {
314 let t = v.trim().to_lowercase();
315 if t.is_empty() || t == "auto" {
316 String::from(items_default)
317 } else {
318 t
319 }
320 }
321 None => String::from(items_default),
322 };
323 match raw.as_str() {
324 "center" => "center",
325 "end" | "flex-end" | "self-end" | "right" => "end",
326 "start" | "flex-start" | "self-start" | "left" => "start",
327 _ => "stretch",
328 }
329}
330