1use super::platform_drawing_context::ImplRef;
2use crate::media::imaging::Bitmap;
3use crate::media::immutable::ImmutablePen;
4use crate::media::{BoxShadows, IBrush, IImmutableBrush, IImmutableGlyphRunReference, IPen};
5use crate::platform::{IBitmapImpl, IDrawingContextImpl};
6use crate::utilities::MathUtilities;
7use crate::{Matrix, Point, Rect, RoundedRect};
8use std::any::{Any, TypeId};
9use std::cell::RefCell;
10use std::rc::Rc;
11
12#[derive(Clone, Copy)]
13struct TransformContainer {
14 local_transform: Matrix,
15 container_transform: Matrix,
16}
17
18#[derive(Clone, Copy)]
19enum PushedStateType {
20 Matrix(Matrix),
21 Opacity,
22 Clip,
23 MatrixContainer,
24 #[allow(dead_code)]
25 GeometryClip,
26 OpacityMask,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32#[must_use = "a pushed state must be popped with `ImmediateDrawingContext::pop`"]
33pub struct ImmediatePushedState {
34 level: usize,
35}
36
37impl ImmediatePushedState {
38 pub const NONE: ImmediatePushedState = ImmediatePushedState { level: 0 };
41}
42
43crate::ferro_thread_local! {
44 static STATE_STACK_POOL: RefCell<Vec<Vec<PushedStateType>>> = const { RefCell::new(Vec::new()) };
45 static TRANSFORM_STACK_POOL: RefCell<Vec<Vec<TransformContainer>>> = const { RefCell::new(Vec::new()) };
46}
47
48const STACK_POOL_LIMIT: usize = 16;
49
50pub struct ImmediateDrawingContext<'a> {
54 platform_impl: ImplRef<'a>,
55 states: Option<Vec<PushedStateType>>,
56 transform_containers: Option<Vec<TransformContainer>>,
57 current_transform: Matrix,
58 current_container_transform: Matrix,
59}
60
61impl<'a> ImmediateDrawingContext<'a> {
62 pub fn new(platform_impl: Box<dyn IDrawingContextImpl + 'a>) -> Self {
65 let transform = platform_impl.transform();
66 Self::create(ImplRef::Owned(platform_impl), transform)
67 }
68
69 pub fn borrowed(platform_impl: &'a mut dyn IDrawingContextImpl) -> Self {
71 let transform = platform_impl.transform();
72 Self::create(ImplRef::Borrowed(platform_impl), transform)
73 }
74
75 pub fn borrowed_with_transform(platform_impl: &'a mut dyn IDrawingContextImpl, transform: Matrix) -> Self {
78 Self::create(ImplRef::Borrowed(platform_impl), transform)
79 }
80
81 fn create(platform_impl: ImplRef<'a>, transform: Matrix) -> Self {
82 Self {
83 platform_impl,
84 states: Some(STATE_STACK_POOL.with(|pool| pool.borrow_mut().pop()).unwrap_or_default()),
85 transform_containers: Some(TRANSFORM_STACK_POOL.with(|pool| pool.borrow_mut().pop()).unwrap_or_default()),
86 current_transform: Matrix::IDENTITY,
87 current_container_transform: transform,
88 }
89 }
90
91 pub fn platform_impl(&mut self) -> &mut (dyn IDrawingContextImpl + 'a) {
93 self.platform_impl.get()
94 }
95
96 pub fn current_transform(&self) -> Matrix {
98 self.current_transform
99 }
100
101 fn set_current_transform(&mut self, value: Matrix) {
102 self.current_transform = value;
103 let transform = self.current_transform * self.current_container_transform;
104 self.platform_impl.get().set_transform(transform);
105 }
106
107 pub fn draw_bitmap(&mut self, source: &Bitmap, rect: Rect) {
109 self.draw_bitmap_rects(source, Rect::from_size(source.size()), rect);
110 }
111
112 pub fn draw_bitmap_rects(&mut self, source: &Bitmap, source_rect: Rect, dest_rect: Rect) {
114 self.platform_impl.get().draw_bitmap(&*source.platform_impl().item(), 1.0, source_rect, dest_rect);
115 }
116
117 pub fn draw_bitmap_impl(&mut self, source: &dyn IBitmapImpl, source_rect: Rect, dest_rect: Rect) {
120 self.platform_impl.get().draw_bitmap(source, 1.0, source_rect, dest_rect);
121 }
122
123 pub fn draw_line(&mut self, pen: &ImmutablePen, p1: Point, p2: Point) {
125 if pen_is_visible(Some(pen)) {
126 self.platform_impl.get().draw_line(Some(pen), p1, p2);
127 }
128 }
129
130 pub fn draw_rectangle(
132 &mut self,
133 brush: Option<&dyn IImmutableBrush>,
134 pen: Option<&ImmutablePen>,
135 rect: Rect,
136 mut radius_x: f64,
137 mut radius_y: f64,
138 box_shadows: &BoxShadows,
139 ) {
140 if brush.is_none() && !pen_is_visible(pen) {
141 return;
142 }
143 if !MathUtilities::is_zero(radius_x) {
144 radius_x = radius_x.min(rect.width / 2.0);
145 }
146 if !MathUtilities::is_zero(radius_y) {
147 radius_y = radius_y.min(rect.height / 2.0);
148 }
149 self.platform_impl.get().draw_rectangle(
150 brush.map(|b| b as &dyn IBrush),
151 pen.map(|p| p as &dyn IPen),
152 RoundedRect::from_radius_xy(rect, radius_x, radius_y),
153 box_shadows,
154 );
155 }
156
157 pub fn draw_rectangle_outline(&mut self, pen: &ImmutablePen, rect: Rect, corner_radius: f32) {
159 let radius = f64::from(corner_radius);
160 self.draw_rectangle(None, Some(pen), rect, radius, radius, &BoxShadows::default());
161 }
162
163 pub fn draw_ellipse(
165 &mut self,
166 brush: Option<&dyn IImmutableBrush>,
167 pen: Option<&ImmutablePen>,
168 center: Point,
169 radius_x: f64,
170 radius_y: f64,
171 ) {
172 if brush.is_none() && !pen_is_visible(pen) {
173 return;
174 }
175 let origin_x = center.x - radius_x;
176 let origin_y = center.y - radius_y;
177 let width = radius_x * 2.0;
178 let height = radius_y * 2.0;
179 self.platform_impl.get().draw_ellipse(
180 brush.map(|b| b as &dyn IBrush),
181 pen.map(|p| p as &dyn IPen),
182 Rect::new(origin_x, origin_y, width, height),
183 );
184 }
185
186 pub fn draw_glyph_run(&mut self, foreground: &dyn IImmutableBrush, glyph_run: &dyn IImmutableGlyphRunReference) {
189 if let Some(glyph_run) = glyph_run.glyph_run() {
190 self.platform_impl.get().draw_glyph_run(Some(foreground as &dyn IBrush), &*glyph_run);
191 }
192 }
193
194 pub fn fill_rectangle(&mut self, brush: &dyn IImmutableBrush, rect: Rect, corner_radius: f32) {
196 let radius = f64::from(corner_radius);
197 self.draw_rectangle(Some(brush), None, rect, radius, radius, &BoxShadows::default());
198 }
199
200 fn pushed(&mut self, state: PushedStateType) -> ImmediatePushedState {
201 let Some(states) = self.states.as_mut() else {
202 panic!("the drawing context has been disposed");
203 };
204 states.push(state);
205 ImmediatePushedState { level: states.len() }
206 }
207
208 pub fn pop(&mut self, state: ImmediatePushedState) {
211 if state.level == 0 {
212 return;
213 }
214 let (Some(states), Some(_)) = (self.states.as_mut(), self.transform_containers.as_ref()) else {
215 panic!("the drawing context has been disposed");
216 };
217 if states.len() != state.level {
218 panic!("Wrong Push/Pop state order");
219 }
220 let restored = states.pop().expect("level is non-zero");
221 self.restore(restored);
222 }
223
224 fn restore(&mut self, state: PushedStateType) {
225 match state {
226 PushedStateType::Matrix(matrix) => self.set_current_transform(matrix),
227 PushedStateType::Clip => self.platform_impl.get().pop_clip(),
228 PushedStateType::Opacity => self.platform_impl.get().pop_opacity(),
229 PushedStateType::GeometryClip => self.platform_impl.get().pop_geometry_clip(),
230 PushedStateType::OpacityMask => self.platform_impl.get().pop_opacity_mask(),
231 PushedStateType::MatrixContainer => {
232 let container = self
233 .transform_containers
234 .as_mut()
235 .and_then(Vec::pop)
236 .expect("a transform container was pushed");
237 self.current_container_transform = container.container_transform;
238 self.set_current_transform(container.local_transform);
239 }
240 }
241 }
242
243 pub fn push_rounded_clip(&mut self, clip: RoundedRect) -> ImmediatePushedState {
245 self.platform_impl.get().push_clip_rounded(clip);
246 self.pushed(PushedStateType::Clip)
247 }
248
249 pub fn push_clip(&mut self, clip: Rect) -> ImmediatePushedState {
251 self.platform_impl.get().push_clip(clip);
252 self.pushed(PushedStateType::Clip)
253 }
254
255 pub fn push_opacity(&mut self, opacity: f64, bounds: Rect) -> ImmediatePushedState {
257 self.platform_impl.get().push_opacity(opacity, Some(bounds));
258 self.pushed(PushedStateType::Opacity)
259 }
260
261 pub fn push_opacity_mask(&mut self, mask: &dyn IImmutableBrush, bounds: Rect) -> ImmediatePushedState {
263 self.platform_impl.get().push_opacity_mask(mask as &dyn IBrush, bounds);
264 self.pushed(PushedStateType::OpacityMask)
265 }
266
267 pub fn push_post_transform(&mut self, matrix: Matrix) -> ImmediatePushedState {
269 self.push_set_transform(self.current_transform * matrix)
270 }
271
272 pub fn push_pre_transform(&mut self, matrix: Matrix) -> ImmediatePushedState {
274 self.push_set_transform(matrix * self.current_transform)
275 }
276
277 pub fn push_set_transform(&mut self, matrix: Matrix) -> ImmediatePushedState {
279 let old_matrix = self.current_transform;
280 self.set_current_transform(matrix);
281 self.pushed(PushedStateType::Matrix(old_matrix))
282 }
283
284 pub fn push_transform_container(&mut self) -> ImmediatePushedState {
286 let Some(containers) = self.transform_containers.as_mut() else {
287 panic!("the drawing context has been disposed");
288 };
289 containers.push(TransformContainer {
290 local_transform: self.current_transform,
291 container_transform: self.current_container_transform,
292 });
293 self.current_container_transform = self.current_transform * self.current_container_transform;
294 self.current_transform = Matrix::IDENTITY;
295 self.pushed(PushedStateType::MatrixContainer)
296 }
297
298 pub fn dispose(mut self) {
301 self.dispose_in_place();
302 }
303
304 fn dispose_in_place(&mut self) {
305 let (Some(mut states), Some(containers)) = (self.states.take(), self.transform_containers.take()) else {
306 return;
307 };
308 self.transform_containers = Some(containers);
310 while let Some(state) = states.pop() {
311 self.restore(state);
312 }
313 let containers = self.transform_containers.take().expect("just set");
314 STATE_STACK_POOL.with(|pool| {
315 let mut pool = pool.borrow_mut();
316 if pool.len() < STACK_POOL_LIMIT {
317 pool.push(states);
318 }
319 });
320 if !containers.is_empty() {
321 panic!("Transform container stack is non-empty");
322 }
323 TRANSFORM_STACK_POOL.with(|pool| {
324 let mut pool = pool.borrow_mut();
325 if pool.len() < STACK_POOL_LIMIT {
326 pool.push(containers);
327 }
328 });
329 if self.platform_impl.is_owned() {
330 self.platform_impl.get().dispose();
331 }
332 }
333
334 pub fn try_get_feature(&mut self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
337 self.platform_impl.get().get_feature(feature_type)
338 }
339}
340
341impl Drop for ImmediateDrawingContext<'_> {
342 fn drop(&mut self) {
343 if !std::thread::panicking() {
344 self.dispose_in_place();
345 }
346 }
347}
348
349fn pen_is_visible(pen: Option<&ImmutablePen>) -> bool {
350 pen.is_some_and(|pen| pen.brush().is_some() && pen.thickness() > 0.0)
351}