1use super::*;
2
3mod geometry;
4mod image;
5mod text;
6
7pub(super) use geometry::*;
8use image::*;
9use text::*;
10
11const RETAINED_TEXT_RUN_CAPACITY: usize = 256;
12const RETAINED_GLYPH_CAPACITY: usize = 4096;
13
14type GlyphPaint = (PhysicalGlyph, usize, CosmicColor);
15
16#[derive(Clone, Copy)]
17struct ByteRun {
18 start: usize,
19 end: usize,
20 is_emoji: bool,
21}
22
23#[derive(Clone)]
24struct TextBufferRun {
25 source: Arc<str>,
26 byte_run: ByteRun,
27 style: TextStyle,
28 emoji_family: Option<String>,
29}
30
31impl TextBufferRun {
32 fn text(&self) -> &str {
33 &self.source[self.byte_run.start..self.byte_run.end]
34 }
35
36 fn is_emoji(&self) -> bool {
37 self.byte_run.is_emoji
38 }
39}
40
41pub struct FontCtx {
42 font_system: FontSystem,
43 swash_cache: SwashCache,
44 text_buffer: Buffer,
45 text_runs: Vec<TextBufferRun>,
46 glyphs: Vec<GlyphPaint>,
47}
48
49#[derive(Clone, Debug, Default)]
50pub struct FontCtxOptions {
51 sources: Option<Vec<FontSource>>,
52}
53
54impl FontCtxOptions {
55 pub fn system() -> Self {
56 Self { sources: None }
57 }
58
59 pub fn from_font_sources(fonts: impl IntoIterator<Item = FontSource>) -> Self {
60 Self {
61 sources: Some(fonts.into_iter().collect()),
62 }
63 }
64
65 fn create(&self) -> FontCtx {
66 match &self.sources {
67 Some(sources) => FontCtx::new_with_font_sources(sources.iter().cloned()),
68 None => FontCtx::new(),
69 }
70 }
71}
72
73pub struct LazyFontCtx {
74 options: FontCtxOptions,
75 fonts: Option<FontCtx>,
76}
77
78impl LazyFontCtx {
79 pub fn new() -> Self {
80 Self::with_options(FontCtxOptions::system())
81 }
82
83 pub fn with_options(options: FontCtxOptions) -> Self {
84 Self {
85 options,
86 fonts: None,
87 }
88 }
89
90 pub fn is_loaded(&self) -> bool {
91 self.fonts.is_some()
92 }
93
94 pub fn get(&mut self) -> &mut FontCtx {
95 if self.fonts.is_none() {
96 self.fonts = Some(self.options.create());
97 }
98 self.fonts.as_mut().expect("font context was just created")
99 }
100
101 pub fn clear_raster_cache(&mut self) {
102 if let Some(fonts) = &mut self.fonts {
103 fonts.clear_raster_cache();
104 }
105 }
106
107 pub fn trim_scratch(&mut self) {
108 if let Some(fonts) = &mut self.fonts {
109 fonts.trim_scratch();
110 }
111 }
112
113 pub fn trim_frame_memory(&mut self) {
114 self.clear_raster_cache();
115 self.trim_scratch();
116 }
117
118 pub fn release(&mut self) {
119 self.fonts = None;
120 crate::memory::trim_free_heap_pages();
121 }
122}
123
124impl Default for LazyFontCtx {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130impl FontCtx {
131 pub fn new() -> Self {
132 Self::from_font_system(FontSystem::new())
133 }
134
135 pub fn new_with_font_sources(fonts: impl IntoIterator<Item = FontSource>) -> Self {
136 let mut db = fontdb::Database::new();
137 for source in fonts {
138 db.load_font_source(source);
139 }
140 let default_family = db
141 .faces()
142 .find_map(|face| face.families.first().map(|(family, _)| family.clone()));
143 if let Some(family) = default_family {
144 db.set_sans_serif_family(family.clone());
145 db.set_serif_family(family.clone());
146 db.set_monospace_family(family);
147 }
148
149 Self::from_font_system(FontSystem::new_with_locale_and_db("en-US".into(), db))
150 }
151
152 fn from_font_system(font_system: FontSystem) -> Self {
153 Self {
154 font_system,
155 swash_cache: SwashCache::new(),
156 text_buffer: Buffer::new_empty(Metrics::new(1.0, 1.3)),
157 text_runs: Vec::new(),
158 glyphs: Vec::new(),
159 }
160 }
161
162 pub fn clear_raster_cache(&mut self) {
163 self.swash_cache = SwashCache::new();
164 }
165
166 pub fn trim_scratch(&mut self) {
167 self.text_buffer = Buffer::new_empty(Metrics::new(1.0, 1.3));
168 trim_vec_capacity(&mut self.text_runs, RETAINED_TEXT_RUN_CAPACITY);
169 trim_vec_capacity(&mut self.glyphs, RETAINED_GLYPH_CAPACITY);
170 }
171}
172
173impl Default for FontCtx {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179fn trim_vec_capacity<T>(values: &mut Vec<T>, retained_capacity: usize) {
180 if values.capacity() > retained_capacity {
181 values.clear();
182 values.shrink_to(retained_capacity);
183 }
184}
185
186mod paint;
187mod rect;
188
189pub use paint::PaintTransform;
190use paint::*;
191pub use rect::{Rect, Ui};
192
193struct UiRenderer {
194 root: Rect,
195 fonts: LazyFontCtx,
196}
197
198impl Renderer for UiRenderer {
199 fn draw(&mut self, canvas: &mut Canvas<'_>, context: RenderContext) -> FrameAction {
200 canvas.clear(Color::TRANSPARENT.into());
201 let fonts = self.fonts.get();
202 self.root.paint_scaled_with_fonts(
203 canvas,
204 fonts,
205 context.width,
206 context.height,
207 context.scale,
208 );
209 self.fonts.trim_frame_memory();
210 FrameAction::Wait
211 }
212
213 fn closed_surface(&mut self, _: SurfaceId) {
214 self.fonts.release();
215 }
216}
217
218fn measure_element(
219 element: &Rect,
220 fonts: &mut FontCtx,
221 available_width: u32,
222 available_height: u32,
223) -> Size {
224 let content_available =
225 Bounds::new(0, 0, available_width, available_height).inset(element.padding);
226 let content_size = match &element.content {
227 Some(Content::Text(text)) => {
228 measure_text(fonts, TextContent::Plain(text), content_available.width)
229 }
230 Some(Content::RichText(text)) => {
231 measure_text(fonts, TextContent::Rich(text), content_available.width)
232 }
233 Some(Content::Image(image)) => {
234 measure_image(image, content_available.width, content_available.height)
235 }
236 None => Size::default(),
237 };
238 let child_size = measure_children(
239 element,
240 fonts,
241 content_available.width,
242 content_available.height,
243 );
244 let measured = Size::new(
245 content_size.width.max(child_size.width),
246 content_size.height.max(child_size.height),
247 );
248 Size::new(
249 constrain_dimension(
250 measured
251 .width
252 .saturating_add(element.padding.left)
253 .saturating_add(element.padding.right)
254 .min(available_width),
255 element.min_width,
256 element.max_width,
257 ),
258 constrain_dimension(
259 measured
260 .height
261 .saturating_add(element.padding.top)
262 .saturating_add(element.padding.bottom)
263 .min(available_height),
264 element.min_height,
265 element.max_height,
266 ),
267 )
268}
269
270fn measure_children(
271 parent: &Rect,
272 fonts: &mut FontCtx,
273 available_width: u32,
274 available_height: u32,
275) -> Size {
276 if parent
277 .children
278 .iter()
279 .all(|child| child.position != Position::Flow)
280 {
281 return Size::default();
282 }
283
284 let direction = parent.direction;
285 let axis_available = match direction {
286 Direction::Row => available_width,
287 Direction::Column => available_height,
288 };
289 let cross_available = match direction {
290 Direction::Row => available_height,
291 Direction::Column => available_width,
292 };
293 let flow_count = parent
294 .children
295 .iter()
296 .filter(|child| child.position == Position::Flow)
297 .count();
298 let gap_total = parent
299 .gap
300 .saturating_mul(flow_count.saturating_sub(1) as u32);
301 let axis_available_without_gaps = axis_available.saturating_sub(gap_total);
302 let mut axis_used = 0_u32;
303 let mut cross_used = 0_u32;
304
305 for child in parent
306 .children
307 .iter()
308 .filter(|child| child.position == Position::Flow)
309 {
310 let measured = measure_element(child, fonts, available_width, available_height);
311 let axis = match axis_length(child, direction) {
312 Length::Fill => constrain_axis(measured.axis(direction), child, direction),
313 _ => resolve_axis_length(
314 child,
315 direction,
316 axis_available_without_gaps,
317 measured.axis(direction),
318 ),
319 };
320 let cross = match cross_length(child, direction) {
321 Length::Fill => constrain_cross(measured.cross(direction), child, direction),
322 _ => resolve_cross_length(child, direction, cross_available, measured.cross(direction)),
323 };
324 axis_used = axis_used.saturating_add(axis);
325 cross_used = cross_used.max(cross);
326 }
327
328 axis_used = axis_used.saturating_add(gap_total).min(axis_available);
329 match direction {
330 Direction::Row => Size::new(axis_used, cross_used.min(cross_available)),
331 Direction::Column => Size::new(cross_used.min(cross_available), axis_used),
332 }
333}
334
335fn layout_children(
336 parent: &Rect,
337 content: Bounds,
338 fonts: &mut FontCtx,
339 mut layout_child: impl FnMut(usize, &Rect, Bounds, Size, &mut FontCtx),
340) {
341 let count = parent
342 .children
343 .iter()
344 .filter(|child| child.position == Position::Flow)
345 .count();
346 if count == 0 {
347 layout_absolute_children(parent, content, fonts, layout_child);
348 return;
349 }
350
351 let gap_total = parent.gap.saturating_mul(count.saturating_sub(1) as u32);
352 let axis_total = match parent.direction {
353 Direction::Row => content.width,
354 Direction::Column => content.height,
355 };
356 let axis_available = axis_total.saturating_sub(gap_total);
357 let cross_available = match parent.direction {
358 Direction::Row => content.height,
359 Direction::Column => content.width,
360 };
361
362 let mut fixed_axis = 0_u32;
363 let mut fill_weight = 0_u32;
364 for child in parent
365 .children
366 .iter()
367 .filter(|child| child.position == Position::Flow)
368 {
369 match axis_length(child, parent.direction) {
370 Length::Fill => {
371 fill_weight = fill_weight.saturating_add(child.fill.max(1));
372 }
373 _ => {
374 let measured = measure_element(child, fonts, content.width, content.height);
375 let axis = resolve_axis_length(
376 child,
377 parent.direction,
378 axis_available,
379 measured.axis(parent.direction),
380 );
381 fixed_axis = fixed_axis.saturating_add(axis);
382 }
383 }
384 }
385
386 let remaining = axis_available.saturating_sub(fixed_axis);
387 let mut distributed = 0_u32;
388 let mut remaining_weight = fill_weight;
389 let mut used_axis = gap_total;
390 for child in parent
391 .children
392 .iter()
393 .filter(|child| child.position == Position::Flow)
394 {
395 let axis = match axis_length(child, parent.direction) {
396 Length::Fill => {
397 let weight = child.fill.max(1);
398 let share = if remaining_weight <= weight {
399 remaining.saturating_sub(distributed)
400 } else {
401 remaining.saturating_sub(distributed) * weight / remaining_weight
402 };
403 remaining_weight = remaining_weight.saturating_sub(weight);
404 distributed = distributed.saturating_add(share);
405 constrain_axis(share, child, parent.direction)
406 }
407 _ => {
408 let measured = measure_element(child, fonts, content.width, content.height);
409 resolve_axis_length(
410 child,
411 parent.direction,
412 axis_available,
413 measured.axis(parent.direction),
414 )
415 }
416 };
417 used_axis = used_axis.saturating_add(axis);
418 }
419
420 let start_offset = align_offset(parent.justify, axis_total, used_axis);
421 let mut cursor = match parent.direction {
422 Direction::Row => content.x.saturating_add(start_offset),
423 Direction::Column => content.y.saturating_add(start_offset),
424 };
425
426 let mut distributed = 0_u32;
427 let mut remaining_weight = fill_weight;
428 for (index, child) in parent
429 .children
430 .iter()
431 .enumerate()
432 .filter(|(_, child)| child.position == Position::Flow)
433 {
434 let measured = measure_element(child, fonts, content.width, content.height);
435 let axis = match axis_length(child, parent.direction) {
436 Length::Fill => {
437 let weight = child.fill.max(1);
438 let share = if remaining_weight <= weight {
439 remaining.saturating_sub(distributed)
440 } else {
441 remaining.saturating_sub(distributed) * weight / remaining_weight
442 };
443 remaining_weight = remaining_weight.saturating_sub(weight);
444 distributed = distributed.saturating_add(share);
445 constrain_axis(share, child, parent.direction)
446 }
447 _ => resolve_axis_length(
448 child,
449 parent.direction,
450 axis_available,
451 measured.axis(parent.direction),
452 ),
453 };
454 if axis == 0 {
455 continue;
456 };
457 let cross = match cross_length(child, parent.direction) {
458 Length::Fill => cross_available,
459 Length::Fit if parent.align == Align::Stretch => cross_available,
460 _ => resolve_cross_length(
461 child,
462 parent.direction,
463 cross_available,
464 measured.cross(parent.direction),
465 ),
466 };
467 let cross_offset = align_offset(parent.align, cross_available, cross);
468 let rect = match parent.direction {
469 Direction::Row => Bounds {
470 x: cursor,
471 y: content.y.saturating_add(cross_offset),
472 width: axis,
473 height: cross,
474 },
475 Direction::Column => Bounds {
476 x: content.x.saturating_add(cross_offset),
477 y: cursor,
478 width: cross,
479 height: axis,
480 },
481 };
482 cursor = cursor.saturating_add(axis).saturating_add(parent.gap);
483 layout_child(index, child, rect, measured, fonts);
484 }
485
486 layout_absolute_children(parent, content, fonts, layout_child);
487}
488
489fn layout_absolute_children(
490 parent: &Rect,
491 content: Bounds,
492 fonts: &mut FontCtx,
493 mut layout_child: impl FnMut(usize, &Rect, Bounds, Size, &mut FontCtx),
494) {
495 for (index, child) in parent
496 .children
497 .iter()
498 .enumerate()
499 .filter(|(_, child)| child.position == Position::Absolute)
500 {
501 let (rect, measured) = absolute_child_rect(child, content, fonts);
502 if rect.width == 0 || rect.height == 0 {
503 continue;
504 }
505 layout_child(index, child, rect, measured, fonts);
506 }
507}
508
509fn absolute_child_rect(child: &Rect, content: Bounds, fonts: &mut FontCtx) -> (Bounds, Size) {
510 let left = child.inset.left.unwrap_or(0);
511 let right = child.inset.right.unwrap_or(0);
512 let top = child.inset.top.unwrap_or(0);
513 let bottom = child.inset.bottom.unwrap_or(0);
514 let available_width = content.width.saturating_sub(left).saturating_sub(right);
515 let available_height = content.height.saturating_sub(top).saturating_sub(bottom);
516 let measured = measure_element(child, fonts, available_width, available_height);
517 let width = if child.inset.left.is_some()
518 && child.inset.right.is_some()
519 && matches!(child.width, Length::Fill)
520 {
521 constrain_dimension(available_width, child.min_width, child.max_width)
522 } else {
523 resolve_length(
524 child.width,
525 available_width,
526 measured.width,
527 child.min_width,
528 child.max_width,
529 )
530 };
531 let height = if child.inset.top.is_some()
532 && child.inset.bottom.is_some()
533 && matches!(child.height, Length::Fill)
534 {
535 constrain_dimension(available_height, child.min_height, child.max_height)
536 } else {
537 resolve_length(
538 child.height,
539 available_height,
540 measured.height,
541 child.min_height,
542 child.max_height,
543 )
544 };
545 let x = match (child.inset.left, child.inset.right) {
546 (Some(left), _) => content.x.saturating_add(left),
547 (None, Some(right)) => content.right().saturating_sub(right).saturating_sub(width),
548 (None, None) => content.x,
549 };
550 let y = match (child.inset.top, child.inset.bottom) {
551 (Some(top), _) => content.y.saturating_add(top),
552 (None, Some(bottom)) => content
553 .bottom()
554 .saturating_sub(bottom)
555 .saturating_sub(height),
556 (None, None) => content.y,
557 };
558
559 (
560 Bounds {
561 x,
562 y,
563 width,
564 height,
565 },
566 measured,
567 )
568}
569
570fn axis_length(element: &Rect, direction: Direction) -> Length {
571 match direction {
572 Direction::Row => element.width,
573 Direction::Column => element.height,
574 }
575}
576
577fn cross_length(element: &Rect, direction: Direction) -> Length {
578 match direction {
579 Direction::Row => element.height,
580 Direction::Column => element.width,
581 }
582}
583
584fn resolve_axis_length(element: &Rect, direction: Direction, available: u32, fit: u32) -> u32 {
585 let (min, max) = match direction {
586 Direction::Row => (element.min_width, element.max_width),
587 Direction::Column => (element.min_height, element.max_height),
588 };
589 resolve_length(axis_length(element, direction), available, fit, min, max)
590}
591
592fn resolve_cross_length(element: &Rect, direction: Direction, available: u32, fit: u32) -> u32 {
593 let (min, max) = match direction {
594 Direction::Row => (element.min_height, element.max_height),
595 Direction::Column => (element.min_width, element.max_width),
596 };
597 resolve_length(cross_length(element, direction), available, fit, min, max)
598}
599
600fn constrain_axis(size: u32, element: &Rect, direction: Direction) -> u32 {
601 let (min, max) = match direction {
602 Direction::Row => (element.min_width, element.max_width),
603 Direction::Column => (element.min_height, element.max_height),
604 };
605 constrain_dimension(size, min, max)
606}
607
608fn constrain_cross(size: u32, element: &Rect, direction: Direction) -> u32 {
609 let (min, max) = match direction {
610 Direction::Row => (element.min_height, element.max_height),
611 Direction::Column => (element.min_width, element.max_width),
612 };
613 constrain_dimension(size, min, max)
614}
615
616fn resolve_length(length: Length, available: u32, fit: u32, min: u32, max: Option<u32>) -> u32 {
617 let resolved = match length {
618 Length::Fit => fit,
619 Length::Fill => available,
620 Length::Px(value) => value.min(available),
621 Length::Percent(percent) => {
622 ((available as f32 * percent.clamp(0.0, 100.0) / 100.0).round() as u32).min(available)
623 }
624 };
625 constrain_dimension(resolved, min, max)
626}
627
628fn constrain_dimension(size: u32, min: u32, max: Option<u32>) -> u32 {
629 let max = max.unwrap_or(u32::MAX).max(min);
630 size.max(min).min(max)
631}