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
189use paint::*;
190pub use rect::{Rect, Ui};
191
192struct UiRenderer {
193 root: Rect,
194 fonts: LazyFontCtx,
195}
196
197impl Renderer for UiRenderer {
198 fn draw(&mut self, canvas: &mut Canvas<'_>, context: RenderContext) -> FrameAction {
199 canvas.clear(Color::TRANSPARENT.into());
200 let fonts = self.fonts.get();
201 self.root.paint_scaled_with_fonts(
202 canvas,
203 fonts,
204 context.width,
205 context.height,
206 context.scale,
207 );
208 self.fonts.trim_frame_memory();
209 FrameAction::Wait
210 }
211
212 fn closed_surface(&mut self, _: SurfaceId) {
213 self.fonts.release();
214 }
215}
216
217fn measure_element(
218 element: &Rect,
219 fonts: &mut FontCtx,
220 available_width: u32,
221 available_height: u32,
222) -> Size {
223 let content_available =
224 Bounds::new(0, 0, available_width, available_height).inset(element.padding);
225 let content_size = match &element.content {
226 Some(Content::Text(text)) => {
227 measure_text(fonts, TextContent::Plain(text), content_available.width)
228 }
229 Some(Content::RichText(text)) => {
230 measure_text(fonts, TextContent::Rich(text), content_available.width)
231 }
232 Some(Content::Image(image)) => {
233 measure_image(image, content_available.width, content_available.height)
234 }
235 None => Size::default(),
236 };
237 let child_size = measure_children(
238 element,
239 fonts,
240 content_available.width,
241 content_available.height,
242 );
243 let measured = Size::new(
244 content_size.width.max(child_size.width),
245 content_size.height.max(child_size.height),
246 );
247 Size::new(
248 constrain_dimension(
249 measured
250 .width
251 .saturating_add(element.padding.left)
252 .saturating_add(element.padding.right)
253 .min(available_width),
254 element.min_width,
255 element.max_width,
256 ),
257 constrain_dimension(
258 measured
259 .height
260 .saturating_add(element.padding.top)
261 .saturating_add(element.padding.bottom)
262 .min(available_height),
263 element.min_height,
264 element.max_height,
265 ),
266 )
267}
268
269fn measure_children(
270 parent: &Rect,
271 fonts: &mut FontCtx,
272 available_width: u32,
273 available_height: u32,
274) -> Size {
275 if parent
276 .children
277 .iter()
278 .all(|child| child.position != Position::Flow)
279 {
280 return Size::default();
281 }
282
283 let direction = parent.direction;
284 let axis_available = match direction {
285 Direction::Row => available_width,
286 Direction::Column => available_height,
287 };
288 let cross_available = match direction {
289 Direction::Row => available_height,
290 Direction::Column => available_width,
291 };
292 let flow_count = parent
293 .children
294 .iter()
295 .filter(|child| child.position == Position::Flow)
296 .count();
297 let gap_total = parent
298 .gap
299 .saturating_mul(flow_count.saturating_sub(1) as u32);
300 let axis_available_without_gaps = axis_available.saturating_sub(gap_total);
301 let mut axis_used = 0_u32;
302 let mut cross_used = 0_u32;
303
304 for child in parent
305 .children
306 .iter()
307 .filter(|child| child.position == Position::Flow)
308 {
309 let measured = measure_element(child, fonts, available_width, available_height);
310 let axis = match axis_length(child, direction) {
311 Length::Fill => constrain_axis(measured.axis(direction), child, direction),
312 _ => resolve_axis_length(
313 child,
314 direction,
315 axis_available_without_gaps,
316 measured.axis(direction),
317 ),
318 };
319 let cross = match cross_length(child, direction) {
320 Length::Fill => constrain_cross(measured.cross(direction), child, direction),
321 _ => resolve_cross_length(child, direction, cross_available, measured.cross(direction)),
322 };
323 axis_used = axis_used.saturating_add(axis);
324 cross_used = cross_used.max(cross);
325 }
326
327 axis_used = axis_used.saturating_add(gap_total).min(axis_available);
328 match direction {
329 Direction::Row => Size::new(axis_used, cross_used.min(cross_available)),
330 Direction::Column => Size::new(cross_used.min(cross_available), axis_used),
331 }
332}
333
334fn layout_children(
335 parent: &Rect,
336 content: Bounds,
337 fonts: &mut FontCtx,
338 mut layout_child: impl FnMut(usize, &Rect, Bounds, Size, &mut FontCtx),
339) {
340 let count = parent
341 .children
342 .iter()
343 .filter(|child| child.position == Position::Flow)
344 .count();
345 if count == 0 {
346 layout_absolute_children(parent, content, fonts, layout_child);
347 return;
348 }
349
350 let gap_total = parent.gap.saturating_mul(count.saturating_sub(1) as u32);
351 let axis_total = match parent.direction {
352 Direction::Row => content.width,
353 Direction::Column => content.height,
354 };
355 let axis_available = axis_total.saturating_sub(gap_total);
356 let cross_available = match parent.direction {
357 Direction::Row => content.height,
358 Direction::Column => content.width,
359 };
360
361 let mut fixed_axis = 0_u32;
362 let mut fill_weight = 0_u32;
363 for child in parent
364 .children
365 .iter()
366 .filter(|child| child.position == Position::Flow)
367 {
368 match axis_length(child, parent.direction) {
369 Length::Fill => {
370 fill_weight = fill_weight.saturating_add(child.fill.max(1));
371 }
372 _ => {
373 let measured = measure_element(child, fonts, content.width, content.height);
374 let axis = resolve_axis_length(
375 child,
376 parent.direction,
377 axis_available,
378 measured.axis(parent.direction),
379 );
380 fixed_axis = fixed_axis.saturating_add(axis);
381 }
382 }
383 }
384
385 let remaining = axis_available.saturating_sub(fixed_axis);
386 let mut distributed = 0_u32;
387 let mut remaining_weight = fill_weight;
388 let mut used_axis = gap_total;
389 for child in parent
390 .children
391 .iter()
392 .filter(|child| child.position == Position::Flow)
393 {
394 let axis = match axis_length(child, parent.direction) {
395 Length::Fill => {
396 let weight = child.fill.max(1);
397 let share = if remaining_weight <= weight {
398 remaining.saturating_sub(distributed)
399 } else {
400 remaining.saturating_sub(distributed) * weight / remaining_weight
401 };
402 remaining_weight = remaining_weight.saturating_sub(weight);
403 distributed = distributed.saturating_add(share);
404 constrain_axis(share, child, parent.direction)
405 }
406 _ => {
407 let measured = measure_element(child, fonts, content.width, content.height);
408 resolve_axis_length(
409 child,
410 parent.direction,
411 axis_available,
412 measured.axis(parent.direction),
413 )
414 }
415 };
416 used_axis = used_axis.saturating_add(axis);
417 }
418
419 let start_offset = align_offset(parent.justify, axis_total, used_axis);
420 let mut cursor = match parent.direction {
421 Direction::Row => content.x.saturating_add(start_offset),
422 Direction::Column => content.y.saturating_add(start_offset),
423 };
424
425 let mut distributed = 0_u32;
426 let mut remaining_weight = fill_weight;
427 for (index, child) in parent
428 .children
429 .iter()
430 .enumerate()
431 .filter(|(_, child)| child.position == Position::Flow)
432 {
433 let measured = measure_element(child, fonts, content.width, content.height);
434 let axis = match axis_length(child, parent.direction) {
435 Length::Fill => {
436 let weight = child.fill.max(1);
437 let share = if remaining_weight <= weight {
438 remaining.saturating_sub(distributed)
439 } else {
440 remaining.saturating_sub(distributed) * weight / remaining_weight
441 };
442 remaining_weight = remaining_weight.saturating_sub(weight);
443 distributed = distributed.saturating_add(share);
444 constrain_axis(share, child, parent.direction)
445 }
446 _ => resolve_axis_length(
447 child,
448 parent.direction,
449 axis_available,
450 measured.axis(parent.direction),
451 ),
452 };
453 if axis == 0 {
454 continue;
455 };
456 let cross = match cross_length(child, parent.direction) {
457 Length::Fill => cross_available,
458 Length::Fit if parent.align == Align::Stretch => cross_available,
459 _ => resolve_cross_length(
460 child,
461 parent.direction,
462 cross_available,
463 measured.cross(parent.direction),
464 ),
465 };
466 let cross_offset = align_offset(parent.align, cross_available, cross);
467 let rect = match parent.direction {
468 Direction::Row => Bounds {
469 x: cursor,
470 y: content.y.saturating_add(cross_offset),
471 width: axis,
472 height: cross,
473 },
474 Direction::Column => Bounds {
475 x: content.x.saturating_add(cross_offset),
476 y: cursor,
477 width: cross,
478 height: axis,
479 },
480 };
481 cursor = cursor.saturating_add(axis).saturating_add(parent.gap);
482 layout_child(index, child, rect, measured, fonts);
483 }
484
485 layout_absolute_children(parent, content, fonts, layout_child);
486}
487
488fn layout_absolute_children(
489 parent: &Rect,
490 content: Bounds,
491 fonts: &mut FontCtx,
492 mut layout_child: impl FnMut(usize, &Rect, Bounds, Size, &mut FontCtx),
493) {
494 for (index, child) in parent
495 .children
496 .iter()
497 .enumerate()
498 .filter(|(_, child)| child.position == Position::Absolute)
499 {
500 let (rect, measured) = absolute_child_rect(child, content, fonts);
501 if rect.width == 0 || rect.height == 0 {
502 continue;
503 }
504 layout_child(index, child, rect, measured, fonts);
505 }
506}
507
508fn absolute_child_rect(child: &Rect, content: Bounds, fonts: &mut FontCtx) -> (Bounds, Size) {
509 let left = child.inset.left.unwrap_or(0);
510 let right = child.inset.right.unwrap_or(0);
511 let top = child.inset.top.unwrap_or(0);
512 let bottom = child.inset.bottom.unwrap_or(0);
513 let available_width = content.width.saturating_sub(left).saturating_sub(right);
514 let available_height = content.height.saturating_sub(top).saturating_sub(bottom);
515 let measured = measure_element(child, fonts, available_width, available_height);
516 let width = if child.inset.left.is_some()
517 && child.inset.right.is_some()
518 && matches!(child.width, Length::Fill)
519 {
520 constrain_dimension(available_width, child.min_width, child.max_width)
521 } else {
522 resolve_length(
523 child.width,
524 available_width,
525 measured.width,
526 child.min_width,
527 child.max_width,
528 )
529 };
530 let height = if child.inset.top.is_some()
531 && child.inset.bottom.is_some()
532 && matches!(child.height, Length::Fill)
533 {
534 constrain_dimension(available_height, child.min_height, child.max_height)
535 } else {
536 resolve_length(
537 child.height,
538 available_height,
539 measured.height,
540 child.min_height,
541 child.max_height,
542 )
543 };
544 let x = match (child.inset.left, child.inset.right) {
545 (Some(left), _) => content.x.saturating_add(left),
546 (None, Some(right)) => content.right().saturating_sub(right).saturating_sub(width),
547 (None, None) => content.x,
548 };
549 let y = match (child.inset.top, child.inset.bottom) {
550 (Some(top), _) => content.y.saturating_add(top),
551 (None, Some(bottom)) => content
552 .bottom()
553 .saturating_sub(bottom)
554 .saturating_sub(height),
555 (None, None) => content.y,
556 };
557
558 (
559 Bounds {
560 x,
561 y,
562 width,
563 height,
564 },
565 measured,
566 )
567}
568
569fn axis_length(element: &Rect, direction: Direction) -> Length {
570 match direction {
571 Direction::Row => element.width,
572 Direction::Column => element.height,
573 }
574}
575
576fn cross_length(element: &Rect, direction: Direction) -> Length {
577 match direction {
578 Direction::Row => element.height,
579 Direction::Column => element.width,
580 }
581}
582
583fn resolve_axis_length(element: &Rect, direction: Direction, available: u32, fit: u32) -> u32 {
584 let (min, max) = match direction {
585 Direction::Row => (element.min_width, element.max_width),
586 Direction::Column => (element.min_height, element.max_height),
587 };
588 resolve_length(axis_length(element, direction), available, fit, min, max)
589}
590
591fn resolve_cross_length(element: &Rect, direction: Direction, available: u32, fit: u32) -> u32 {
592 let (min, max) = match direction {
593 Direction::Row => (element.min_height, element.max_height),
594 Direction::Column => (element.min_width, element.max_width),
595 };
596 resolve_length(cross_length(element, direction), available, fit, min, max)
597}
598
599fn constrain_axis(size: u32, element: &Rect, direction: Direction) -> u32 {
600 let (min, max) = match direction {
601 Direction::Row => (element.min_width, element.max_width),
602 Direction::Column => (element.min_height, element.max_height),
603 };
604 constrain_dimension(size, min, max)
605}
606
607fn constrain_cross(size: u32, element: &Rect, direction: Direction) -> u32 {
608 let (min, max) = match direction {
609 Direction::Row => (element.min_height, element.max_height),
610 Direction::Column => (element.min_width, element.max_width),
611 };
612 constrain_dimension(size, min, max)
613}
614
615fn resolve_length(length: Length, available: u32, fit: u32, min: u32, max: Option<u32>) -> u32 {
616 let resolved = match length {
617 Length::Fit => fit,
618 Length::Fill => available,
619 Length::Px(value) => value.min(available),
620 Length::Percent(percent) => {
621 ((available as f32 * percent.clamp(0.0, 100.0) / 100.0).round() as u32).min(available)
622 }
623 };
624 constrain_dimension(resolved, min, max)
625}
626
627fn constrain_dimension(size: u32, min: u32, max: Option<u32>) -> u32 {
628 let max = max.unwrap_or(u32::MAX).max(min);
629 size.max(min).min(max)
630}