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
73#[derive(Clone)]
75pub struct LayoutBox<'a> {
76 pub dimensions: Dimensions,
77 pub box_type: BoxType<'a>,
78 pub children: Vec<LayoutBox<'a>>,
79}
80
81#[derive(Clone, Copy)]
82pub enum BoxType<'a> {
83 BlockNode(&'a StyledNode<'a>),
84 InlineNode(&'a StyledNode<'a>),
85 InlineBlockNode(&'a StyledNode<'a>),
86 FlexNode(&'a StyledNode<'a>),
87 GridNode(&'a StyledNode<'a>),
88 TableNode(&'a StyledNode<'a>),
89 TableRowGroupNode(&'a StyledNode<'a>),
90 TableRowNode(&'a StyledNode<'a>),
91 TableCellNode(&'a StyledNode<'a>),
92 AnonymousBlock,
93}
94
95pub(crate) fn default_display_for(tag_name: &str) -> &'static str {
98 match tag_name {
99 "head" | "title" | "meta" | "link" | "style" | "script" => "none",
100 "html" | "body" | "div" | "p" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "ul" | "ol"
101 | "li" | "form" | "center" | "header" | "footer" | "main" | "section" | "nav" | "hr"
102 | "caption" | "details" | "summary" => "block",
103 "table" => "table",
104 "thead" | "tbody" | "tfoot" => "table-row-group",
105 "tr" => "table-row",
106 "td" | "th" => "table-cell",
107 _ => "inline",
108 }
109}
110
111pub(crate) fn cell_spans(cell: &LayoutBox) -> (usize, usize) {
113 if let BoxType::TableCellNode(styled) = &cell.box_type {
114 if let NodeType::Element { attributes, .. } = &styled.node.node_type {
115 let cs = attributes
116 .get("colspan")
117 .and_then(|v| v.trim().parse::<usize>().ok())
118 .unwrap_or(1)
119 .clamp(1, 256);
120 let rs = attributes
121 .get("rowspan")
122 .and_then(|v| v.trim().parse::<usize>().ok())
123 .unwrap_or(1)
124 .clamp(1, 1024);
125 return (cs, rs);
126 }
127 }
128 (1, 1)
129}
130
131pub(crate) fn box_tag_name<'a>(b: &LayoutBox<'a>) -> Option<&'a str> {
133 let styled = match &b.box_type {
134 BoxType::BlockNode(s)
135 | BoxType::InlineNode(s)
136 | BoxType::InlineBlockNode(s)
137 | BoxType::FlexNode(s)
138 | BoxType::GridNode(s)
139 | BoxType::TableNode(s)
140 | BoxType::TableRowGroupNode(s)
141 | BoxType::TableRowNode(s)
142 | BoxType::TableCellNode(s) => *s,
143 BoxType::AnonymousBlock => return None,
144 };
145 match &styled.node.node_type {
146 NodeType::Element { tag_name, .. } => Some(tag_name.as_str()),
147 NodeType::Text(_) => None,
148 }
149}
150
151pub(crate) fn box_style_value<'a>(b: &LayoutBox<'a>, prop: &str) -> Option<String> {
153 let styled = match &b.box_type {
154 BoxType::BlockNode(s)
155 | BoxType::InlineNode(s)
156 | BoxType::InlineBlockNode(s)
157 | BoxType::FlexNode(s)
158 | BoxType::GridNode(s)
159 | BoxType::TableNode(s)
160 | BoxType::TableRowGroupNode(s)
161 | BoxType::TableRowNode(s)
162 | BoxType::TableCellNode(s) => *s,
163 BoxType::AnonymousBlock => return None,
164 };
165 styled.value(prop)
166}
167
168pub(crate) fn box_has_explicit(b: &LayoutBox, prop: &str) -> bool {
170 box_style_value(b, prop)
171 .map(|v| {
172 let t = v.trim().to_lowercase();
173 !t.is_empty() && t != "auto"
174 })
175 .unwrap_or(false)
176}
177
178pub(crate) fn box_flex_grow(b: &LayoutBox) -> i32 {
180 if let Some(g) = box_style_value(b, "flex-grow") {
181 return g.trim().parse::<i32>().unwrap_or(0).max(0);
182 }
183 if let Some(f) = box_style_value(b, "flex") {
184 let t = f.trim().to_lowercase();
185 if t == "none" || t == "initial" {
186 return 0;
187 }
188 if t == "auto" {
189 return 1; }
191 if let Some(first) = t.split_whitespace().next() {
192 return first.parse::<i32>().unwrap_or(0).max(0);
193 }
194 }
195 0
196}
197
198pub(crate) fn is_flex_length_token(tok: &str) -> bool {
208 for suffix in ["px", "%", "rem", "em", "vh", "vw"] {
209 if let Some(n) = tok.strip_suffix(suffix) {
210 if n.trim().parse::<f32>().is_ok() {
211 return true;
212 }
213 }
214 }
215 false
216}
217
218pub(crate) fn box_flex_shrink(b: &LayoutBox) -> i32 {
219 if let Some(s) = box_style_value(b, "flex-shrink") {
220 return s.trim().parse::<i32>().unwrap_or(1).max(0);
221 }
222 if let Some(f) = box_style_value(b, "flex") {
223 let t = f.trim().to_lowercase();
224 if t == "none" {
225 return 0; }
227 if t == "initial" {
228 return 1; }
230 if t == "auto" {
231 return 1; }
233 let parts: Vec<&str> = t.split_whitespace().collect();
235 if let Some(second) = parts.get(1) {
236 if !is_flex_length_token(second) && *second != "auto" {
238 return second.parse::<i32>().unwrap_or(1).max(0);
239 }
240 }
241 return 1;
243 }
244 1
245}
246
247pub(crate) fn box_order(b: &LayoutBox) -> i32 {
249 box_style_value(b, "order")
250 .and_then(|v| v.trim().parse::<i32>().ok())
251 .unwrap_or(0)
252}
253
254pub(crate) fn box_flex_basis(b: &LayoutBox, main_size: i32) -> Option<i32> {
258 if let Some(v) = box_style_value(b, "flex-basis") {
260 let t = v.trim().to_lowercase();
261 if t.is_empty() || t == "auto" || t == "content" {
262 return None;
263 }
264 return resolve_basis_len(&t, main_size);
265 }
266 if let Some(f) = box_style_value(b, "flex") {
268 let t = f.trim().to_lowercase();
269 if t == "none" || t == "auto" || t == "initial" {
270 return None; }
272 for tok in t.split_whitespace() {
274 if is_flex_length_token(tok) {
275 return resolve_basis_len(tok, main_size);
276 }
277 }
278 if t.split_whitespace().count() == 1 && t.parse::<i32>().is_ok() {
280 return Some(0);
281 }
282 }
283 None
284}
285
286pub(crate) fn resolve_basis_len(tok: &str, main_size: i32) -> Option<i32> {
288 if let Some(p) = tok.strip_suffix('%') {
289 return p
290 .trim()
291 .parse::<f32>()
292 .ok()
293 .map(|pct| round_f32_to_i32(main_size as f32 * pct / 100.0).max(0));
294 }
295 parse_px_like(tok).map(|v| v.max(0))
296}
297
298pub(crate) fn box_align_self(b: &LayoutBox) -> Option<String> {
300 box_style_value(b, "align-self").and_then(|v| {
301 let t = v.trim().to_lowercase();
302 if t.is_empty() || t == "auto" {
303 None
304 } else {
305 Some(t)
306 }
307 })
308}
309
310pub(crate) fn grid_item_align(b: &LayoutBox, self_prop: &str, items_default: &str) -> &'static str {
315 let raw = match box_style_value(b, self_prop) {
316 Some(v) => {
317 let t = v.trim().to_lowercase();
318 if t.is_empty() || t == "auto" {
319 String::from(items_default)
320 } else {
321 t
322 }
323 }
324 None => String::from(items_default),
325 };
326 match raw.as_str() {
327 "center" => "center",
328 "end" | "flex-end" | "self-end" | "right" => "end",
329 "start" | "flex-start" | "self-start" | "left" => "start",
330 _ => "stretch",
331 }
332}
333