Skip to main content

orinium_browser/engine/renderer_model/
draw_command.rs

1//! Draw command definition for rendering, which represents drawing instructions.
2
3use crate::engine::layouter::types::{
4    Background, Color, Gradient, InfoNode, NodeKind, TextDecoration, TextStyle,
5};
6use smol_str::SmolStr;
7use ui_layout::{LayoutChild, LayoutNode};
8
9#[derive(Debug, Clone)]
10pub enum DrawCommand {
11    DrawText {
12        x: f32,
13        y: f32,
14        text: SmolStr,
15        style: TextStyle,
16    },
17    DrawRect {
18        x: f32,
19        y: f32,
20        width: f32,
21        height: f32,
22        color: Color,
23    },
24    DrawGradientRect {
25        x: f32,
26        y: f32,
27        width: f32,
28        height: f32,
29        gradient: Gradient,
30    },
31    DrawPolygon {
32        points: Vec<(f32, f32)>,
33        color: Color,
34    },
35    DrawEllipse {
36        center: (f32, f32),
37        radius_x: f32,
38        radius_y: f32,
39        color: Color,
40    },
41    PushClip {
42        x: f32,
43        y: f32,
44        width: f32,
45        height: f32,
46    },
47    PopClip,
48    PushTransform {
49        dx: f32,
50        dy: f32,
51    },
52    PopTransform,
53}
54
55/// Per-box-model push state for balanced pop generation.
56#[derive(Default, Clone, Copy)]
57struct BoxPushState {
58    border: bool,
59    clip: bool,
60    content: bool,
61    scroll: bool,
62}
63
64/// LayoutNode + InfoNode → DrawCommand
65pub fn generate_draw_commands(
66    cmd_buf: &mut Vec<DrawCommand>,
67    layout: &LayoutNode,
68    info: &InfoNode,
69) {
70    let mut box_states: Vec<BoxPushState> = Vec::new();
71
72    match &info.kind {
73        NodeKind::Text { .. } | NodeKind::LineBreak => unreachable!(),
74
75        NodeKind::Container {
76            scroll_offset_x,
77            scroll_offset_y,
78            style,
79            ..
80        } => {
81            for box_model in &layout.layout_box {
82                let border_box = box_model.border_box;
83                let padding_box = box_model.padding_box;
84                let content_box = box_model.content_box;
85                let is_inline = matches!(layout.layout_box, ui_layout::LayoutBox::InlineBox(_));
86
87                let mut state = BoxPushState::default();
88
89                if border_box.x != 0.0 || border_box.y != 0.0 {
90                    cmd_buf.push(DrawCommand::PushTransform {
91                        dx: border_box.x,
92                        dy: border_box.y,
93                    });
94                    state.border = true;
95                }
96
97                if !is_inline {
98                    // Inline elements are fragmented across lines, so their borders
99                    // cannot be drawn as a single rectangle per box model.
100                    // TODO: support inline borders (draw per fragment).
101                    let bc = &style.border_color;
102
103                    // 各辺の border 幅 = border_box と padding_box の座標差
104                    let bw_top = (padding_box.y - border_box.y).max(0.0);
105                    let bw_bottom = (border_box.y + border_box.height
106                        - (padding_box.y + padding_box.height))
107                        .max(0.0);
108                    let bw_left = (padding_box.x - border_box.x).max(0.0);
109                    let bw_right = (border_box.x + border_box.width
110                        - (padding_box.x + padding_box.width))
111                        .max(0.0);
112
113                    // 上下は full-width、左右は上下と重ならないよう y をずらして高さを詰める。
114                    // CSS 仕様上コーナーでは上下の色が優先される(top > left > bottom > right)。
115                    if bw_top > 0.0 {
116                        cmd_buf.push(DrawCommand::DrawRect {
117                            x: 0.0,
118                            y: 0.0,
119                            width: border_box.width,
120                            height: bw_top,
121                            color: bc.top,
122                        });
123                    }
124
125                    if bw_bottom > 0.0 {
126                        cmd_buf.push(DrawCommand::DrawRect {
127                            x: 0.0,
128                            y: border_box.height - bw_bottom,
129                            width: border_box.width,
130                            height: bw_bottom,
131                            color: bc.bottom,
132                        });
133                    }
134
135                    if bw_left > 0.0 {
136                        cmd_buf.push(DrawCommand::DrawRect {
137                            x: 0.0,
138                            y: bw_top,
139                            width: bw_left,
140                            height: border_box.height - bw_top - bw_bottom,
141                            color: bc.left,
142                        });
143                    }
144
145                    if bw_right > 0.0 {
146                        cmd_buf.push(DrawCommand::DrawRect {
147                            x: border_box.width - bw_right,
148                            y: bw_top,
149                            width: bw_right,
150                            height: border_box.height - bw_top - bw_bottom,
151                            color: bc.right,
152                        });
153                    }
154                }
155
156                if !is_inline && padding_box.width > 0.0 && padding_box.height > 0.0 {
157                    cmd_buf.push(DrawCommand::PushClip {
158                        x: padding_box.x - border_box.x,
159                        y: padding_box.y - border_box.y,
160                        width: padding_box.width,
161                        height: padding_box.height,
162                    });
163                    state.clip = true;
164                }
165
166                match &style.background {
167                    Background::Color(c) if c.3 > 0 => {
168                        cmd_buf.push(DrawCommand::DrawRect {
169                            x: padding_box.x - border_box.x,
170                            y: padding_box.y - border_box.y,
171                            width: padding_box.width,
172                            height: padding_box.height,
173                            color: *c,
174                        });
175                    }
176                    Background::Gradient(g) => {
177                        cmd_buf.push(DrawCommand::DrawGradientRect {
178                            x: padding_box.x - border_box.x,
179                            y: padding_box.y - border_box.y,
180                            width: padding_box.width,
181                            height: padding_box.height,
182                            gradient: g.clone(),
183                        });
184                    }
185                    _ => {}
186                }
187
188                let dx = content_box.x - border_box.x;
189                let dy = content_box.y - border_box.y;
190                if dx != 0.0 || dy != 0.0 {
191                    cmd_buf.push(DrawCommand::PushTransform { dx, dy });
192                    state.content = true;
193                }
194
195                if *scroll_offset_x != 0.0 || *scroll_offset_y != 0.0 {
196                    cmd_buf.push(DrawCommand::PushTransform {
197                        dx: *scroll_offset_x,
198                        dy: -*scroll_offset_y,
199                    });
200                    state.scroll = true;
201                }
202
203                box_states.push(state);
204            }
205        }
206    }
207
208    let mut layout_iter = layout.children.iter();
209
210    for child_info in &info.children {
211        match &child_info.kind {
212            NodeKind::Text { texts, style } => {
213                for text in texts {
214                    if let Some(LayoutChild::Fragment(frag_node)) = layout_iter.next() {
215                        let (x, y) = frag_node.placement.offset;
216
217                        cmd_buf.push(DrawCommand::DrawText {
218                            x,
219                            y,
220                            text: text.into(),
221                            style: *style,
222                        });
223
224                        let font_size = style.font_size;
225                        let line_thickness = (font_size * 0.08).max(1.0);
226                        let (line_y, draw) = match style.text_decoration {
227                            TextDecoration::None => (0.0, false),
228                            TextDecoration::Underline => (y + font_size, true),
229                            TextDecoration::LineThrough => (y + font_size * 0.5, true),
230                            TextDecoration::Overline => (y, true),
231                        };
232
233                        if draw {
234                            cmd_buf.push(DrawCommand::DrawRect {
235                                x,
236                                y: line_y,
237                                width: frag_node.node.width(),
238                                height: line_thickness,
239                                color: style.color,
240                            });
241                        }
242                    }
243                }
244            }
245            NodeKind::LineBreak => {
246                layout_iter.next();
247            }
248            NodeKind::Container { .. } => {
249                if let Some(LayoutChild::Node(node)) = layout_iter.next() {
250                    generate_draw_commands(cmd_buf, node, child_info);
251                }
252            }
253        }
254    }
255
256    // Pop commands for containers (reverse order of pushes)
257    if matches!(info.kind, NodeKind::Container { .. }) {
258        for state in box_states.iter().rev() {
259            if state.scroll {
260                cmd_buf.push(DrawCommand::PopTransform);
261            }
262            if state.content {
263                cmd_buf.push(DrawCommand::PopTransform);
264            }
265            if state.clip {
266                cmd_buf.push(DrawCommand::PopClip);
267            }
268            if state.border {
269                cmd_buf.push(DrawCommand::PopTransform);
270            }
271        }
272    }
273}