1use crate::aes::Aesthetic;
2use crate::annotate::Annotation;
3use crate::build::BuiltPlot;
4use crate::facet::Facet;
5use crate::guide::{axis, legend};
6use crate::render::backend::{DrawBackend, LineStyle, Linetype, RectStyle, TextAnchor, TextStyle};
7use crate::render::RenderError;
8
9pub struct PlotRenderer;
11
12impl PlotRenderer {
13 pub fn render(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
14 if !built.facet.is_none() && !built.panels.is_empty() {
15 Self::render_faceted(built, backend)
16 } else {
17 Self::render_single(built, backend)
18 }
19 }
20
21 fn render_single(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
22 let theme = &built.theme;
23 let plot_area = backend.plot_area();
24 let total_area = backend.total_area();
25
26 if theme.plot_background.visible {
28 if let Some(fill) = theme.plot_background.fill {
29 backend.draw_rect(
30 (total_area.x, total_area.y),
31 (
32 total_area.x + total_area.width,
33 total_area.y + total_area.height,
34 ),
35 &RectStyle {
36 fill: Some(fill),
37 stroke: None,
38 stroke_width: 0.0,
39 alpha: 1.0,
40 clip: false,
41 },
42 )?;
43 }
44 }
45
46 if theme.panel_background.visible {
48 if let Some(fill) = theme.panel_background.fill {
49 backend.draw_rect(
50 (plot_area.x, plot_area.y),
51 (
52 plot_area.x + plot_area.width,
53 plot_area.y + plot_area.height,
54 ),
55 &RectStyle {
56 fill: Some(fill),
57 stroke: theme.panel_background.color,
58 stroke_width: theme.panel_background.width,
59 alpha: 1.0,
60 clip: false,
61 },
62 )?;
63 }
64 }
65
66 if theme.panel_border.visible {
68 let style = LineStyle {
69 color: theme.panel_border.color,
70 width: theme.panel_border.width,
71 alpha: 1.0,
72 linetype: Linetype::Solid,
73 };
74 let x0 = plot_area.x;
75 let y0 = plot_area.y;
76 let x1 = plot_area.x + plot_area.width;
77 let y1 = plot_area.y + plot_area.height;
78 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
79 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
80 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
81 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
82 }
83
84 let x_scale = built.scales.get(&Aesthetic::X);
86 let y_scale = built.scales.get(&Aesthetic::Y);
87
88 let (h_scale, v_scale) = if built.coord.is_flipped() {
89 (y_scale, x_scale)
90 } else {
91 (x_scale, y_scale)
92 };
93
94 if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
95 if built.coord.gridlines() && !theme.panel_ontop {
97 axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
98 }
99
100 axis::draw_x_axis(hs, built.coord.as_ref(), theme, &plot_area, backend)?;
102 axis::draw_y_axis(vs, built.coord.as_ref(), theme, &plot_area, backend)?;
103
104 if let Some(sec) = built.scales.sec_axis(&Aesthetic::Y) {
106 axis::draw_sec_y_axis(vs, sec, built.coord.as_ref(), theme, &plot_area, backend)?;
107 }
108 }
109
110 for layer in &built.layers {
112 layer.geom.draw(
113 &layer.data,
114 built.coord.as_ref(),
115 &built.scales,
116 theme,
117 backend,
118 )?;
119 }
120
121 if theme.panel_ontop && built.coord.gridlines() {
123 if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
124 axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
125 }
126 }
127
128 Self::draw_annotations(
130 &built.annotations,
131 &built.scales,
132 built.coord.as_ref(),
133 &plot_area,
134 backend,
135 )?;
136
137 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
139 if let Some(ref title) = built.labels.title {
140 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
141 let x_axis_top = built
144 .scales
145 .get(&Aesthetic::X)
146 .map(|s| s.axis_position_opposite())
147 .unwrap_or(false);
148 let x_axis_lift = if x_axis_top {
149 theme.axis_ticks_length + theme.axis_text_x.size + theme.legend_spacing
150 } else {
151 0.0
152 };
153 let title_y = plot_area.y - theme.title.size * 0.9 - x_axis_lift;
154 let family = if theme.title.family.is_empty() {
155 None
156 } else {
157 Some(theme.title.family.clone())
158 };
159 backend.draw_text(
160 title,
161 (tx, title_y.max(theme.title.size)),
162 &TextStyle {
163 color: theme.title.color,
164 size: theme.title.size,
165 anchor,
166 angle: 0.0,
167 family,
168 face: theme.title.face,
169 },
170 )?;
171 }
172
173 if let Some(ref subtitle) = built.labels.subtitle {
175 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
176 let subtitle_y = plot_area.y - 2.0;
177 let family = if theme.subtitle.family.is_empty() {
178 None
179 } else {
180 Some(theme.subtitle.family.clone())
181 };
182 backend.draw_text(
183 subtitle,
184 (sx, subtitle_y.max(theme.title.size + theme.subtitle.size)),
185 &TextStyle {
186 color: theme.subtitle.color,
187 size: theme.subtitle.size,
188 anchor,
189 angle: 0.0,
190 family,
191 face: theme.subtitle.face,
192 },
193 )?;
194 }
195
196 legend::draw_legend(
198 &built.scales,
199 theme,
200 &plot_area,
201 backend,
202 &built.guide_legend,
203 &built.suppressed_aes,
204 )?;
205
206 if let Some(ref caption) = built.labels.caption {
208 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
209 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
210 let family = if theme.caption.family.is_empty() {
211 None
212 } else {
213 Some(theme.caption.family.clone())
214 };
215 backend.draw_text(
216 caption,
217 (cx, caption_y),
218 &TextStyle {
219 color: theme.caption.color,
220 size: theme.caption.size,
221 anchor,
222 angle: 0.0,
223 family,
224 face: theme.caption.face,
225 },
226 )?;
227 }
228
229 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
231
232 Ok(())
233 }
234
235 fn hjust_pos(
238 el: &crate::theme::elements::ElementText,
239 area: &crate::render::Rect,
240 ) -> (f64, TextAnchor) {
241 let x = area.x + el.hjust.clamp(0.0, 1.0) * area.width;
242 let anchor = if el.hjust <= 0.02 {
243 TextAnchor::Start
244 } else if el.hjust >= 0.98 {
245 TextAnchor::End
246 } else {
247 TextAnchor::Middle
248 };
249 (x, anchor)
250 }
251
252 fn title_ref_rect(
255 theme: &crate::theme::Theme,
256 plot_area: &crate::render::Rect,
257 total_area: &crate::render::Rect,
258 ) -> crate::render::Rect {
259 match theme.title_position {
260 crate::theme::TitlePosition::Panel => plot_area.clone(),
261 crate::theme::TitlePosition::Plot => crate::render::Rect {
262 x: total_area.x + theme.plot_margin.left,
263 y: plot_area.y,
264 width: (total_area.width - theme.plot_margin.left - theme.plot_margin.right)
265 .max(1.0),
266 height: plot_area.height,
267 },
268 }
269 }
270
271 fn draw_tag(
273 labels: &crate::plot::Labels,
274 theme: &crate::theme::Theme,
275 total_area: &crate::render::Rect,
276 backend: &mut dyn DrawBackend,
277 ) -> Result<(), RenderError> {
278 if let Some(ref tag) = labels.tag {
279 let family = if theme.title.family.is_empty() {
280 None
281 } else {
282 Some(theme.title.family.clone())
283 };
284 use crate::theme::TagPosition::*;
285 let pad = theme.title.size;
286 let (left, right) = (total_area.x + pad, total_area.x + total_area.width - pad);
287 let (top, bottom) = (total_area.y + pad, total_area.y + total_area.height - pad);
288 let (x, y, anchor) = match theme.tag_position {
289 TopLeft => (left, top, TextAnchor::Start),
290 TopRight => (right, top, TextAnchor::End),
291 BottomLeft => (left, bottom, TextAnchor::Start),
292 BottomRight => (right, bottom, TextAnchor::End),
293 };
294 backend.draw_text(
295 tag,
296 (x, y),
297 &TextStyle {
298 color: theme.title.color,
299 size: theme.title.size,
300 anchor,
301 angle: 0.0,
302 family,
303 face: theme.title.face,
304 },
305 )?;
306 }
307 Ok(())
308 }
309
310 fn render_faceted(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
311 let theme = &built.theme;
312 let plot_area = backend.plot_area();
313 let total_area = backend.total_area();
314
315 if theme.plot_background.visible {
317 if let Some(fill) = theme.plot_background.fill {
318 backend.draw_rect(
319 (total_area.x, total_area.y),
320 (
321 total_area.x + total_area.width,
322 total_area.y + total_area.height,
323 ),
324 &RectStyle {
325 fill: Some(fill),
326 stroke: None,
327 stroke_width: 0.0,
328 alpha: 1.0,
329 clip: false,
330 },
331 )?;
332 }
333 }
334
335 let ncol = match &built.facet {
337 Facet::Wrap { ncol, .. } => {
338 ncol.unwrap_or_else(|| (built.panels.len() as f64).sqrt().ceil() as usize)
339 }
340 Facet::Grid { .. } => built.panels.iter().map(|p| p.col).max().unwrap_or(0) + 1,
341 Facet::None => 1,
342 };
343 let nrow = built.panels.len().div_ceil(ncol);
344
345 let strip_height = theme.strip_text.size + 8.0;
346 let gap_x = theme.get_panel_spacing_x();
347 let gap_y = theme.get_panel_spacing_y();
348
349 let space = match &built.facet {
352 Facet::Grid { space, .. } => space.clone(),
353 _ => crate::facet::FacetSpace::Fixed,
354 };
355 let extent = |pi: usize, col: &str| -> Option<f64> {
356 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
357 for df in &built.panels_data[pi] {
358 if let Some(c) = df.column(col) {
359 for v in c {
360 if let Some(f) = v.as_f64() {
361 lo = lo.min(f);
362 hi = hi.max(f);
363 }
364 }
365 }
366 }
367 (lo <= hi).then_some(hi - lo)
368 };
369 let avail_w = plot_area.width - gap_x * (ncol as f64 - 1.0);
370 let col_widths: Vec<f64> = if space.free_x() && ncol > 0 {
371 let mut ranges = vec![0.0f64; ncol];
372 for (pi, panel) in built.panels.iter().enumerate() {
373 if let Some(r) = extent(pi, "x") {
374 ranges[panel.col] = ranges[panel.col].max(r);
375 }
376 }
377 let total: f64 = ranges.iter().sum();
378 if total > 0.0 {
379 ranges.iter().map(|r| avail_w * (r / total)).collect()
380 } else {
381 vec![avail_w / ncol as f64; ncol]
382 }
383 } else {
384 vec![avail_w / ncol.max(1) as f64; ncol.max(1)]
385 };
386 let mut col_x = vec![plot_area.x; ncol.max(1)];
387 {
388 let mut acc = plot_area.x;
389 for c in 0..ncol {
390 col_x[c] = acc;
391 acc += col_widths[c] + gap_x;
392 }
393 }
394
395 let avail_h = plot_area.height - gap_y * (nrow as f64 - 1.0) - strip_height * nrow as f64;
396 let row_heights: Vec<f64> = if space.free_y() && nrow > 0 {
397 let mut ranges = vec![0.0f64; nrow];
398 for (pi, panel) in built.panels.iter().enumerate() {
399 if let Some(r) = extent(pi, "y") {
400 ranges[panel.row] = ranges[panel.row].max(r);
401 }
402 }
403 let total: f64 = ranges.iter().sum();
404 if total > 0.0 {
405 ranges.iter().map(|r| avail_h * (r / total)).collect()
406 } else {
407 vec![avail_h / nrow as f64; nrow]
408 }
409 } else {
410 vec![avail_h / nrow.max(1) as f64; nrow.max(1)]
411 };
412 let mut row_y = vec![plot_area.y; nrow.max(1)];
413 {
414 let mut acc = plot_area.y;
415 for r in 0..nrow {
416 row_y[r] = acc;
417 acc += row_heights[r] + strip_height + gap_y;
418 }
419 }
420
421 for (pi, panel) in built.panels.iter().enumerate() {
422 let panel_width = col_widths[panel.col];
423 let panel_height = row_heights[panel.row];
424 let px = col_x[panel.col];
425 let py = row_y[panel.row];
426
427 let panel_rect = crate::render::Rect {
428 x: px,
429 y: py + strip_height,
430 width: panel_width,
431 height: panel_height,
432 };
433
434 if theme.strip_background.visible {
436 backend.draw_rect(
437 (px, py),
438 (px + panel_width, py + strip_height),
439 &RectStyle {
440 fill: theme.strip_background.fill,
441 stroke: theme.strip_background.color,
442 stroke_width: theme.strip_background.width,
443 alpha: 1.0,
444 clip: false,
445 },
446 )?;
447 }
448
449 if theme.strip_text.visible {
451 let label = panel.col_label.as_deref().unwrap_or(&panel.label);
452 let family = if theme.strip_text.family.is_empty() {
453 None
454 } else {
455 Some(theme.strip_text.family.clone())
456 };
457 backend.draw_text(
458 label,
459 (px + panel_width / 2.0, py + strip_height / 2.0),
460 &TextStyle {
461 color: theme.strip_text.color,
462 size: theme.strip_text.size,
463 anchor: TextAnchor::Middle,
464 angle: 0.0,
465 family,
466 face: theme.strip_text.face,
467 },
468 )?;
469 }
470
471 if theme.panel_background.visible {
473 if let Some(fill) = theme.panel_background.fill {
474 backend.draw_rect(
475 (panel_rect.x, panel_rect.y),
476 (
477 panel_rect.x + panel_rect.width,
478 panel_rect.y + panel_rect.height,
479 ),
480 &RectStyle {
481 fill: Some(fill),
482 stroke: theme.panel_background.color,
483 stroke_width: theme.panel_background.width,
484 alpha: 1.0,
485 clip: false,
486 },
487 )?;
488 }
489 }
490
491 if theme.panel_border.visible {
493 let style = LineStyle {
494 color: theme.panel_border.color,
495 width: theme.panel_border.width,
496 alpha: 1.0,
497 linetype: Linetype::Solid,
498 };
499 let x0 = panel_rect.x;
500 let y0 = panel_rect.y;
501 let x1 = panel_rect.x + panel_rect.width;
502 let y1 = panel_rect.y + panel_rect.height;
503 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
504 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
505 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
506 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
507 }
508
509 let panel_scale_set = if pi < built.panel_scales.len() {
511 &built.panel_scales[pi]
512 } else {
513 &built.scales
514 };
515
516 let x_scale = panel_scale_set.get(&Aesthetic::X);
518 let y_scale = panel_scale_set.get(&Aesthetic::Y);
519
520 if let (Some(xs), Some(ys)) = (x_scale, y_scale) {
521 if built.coord.gridlines() {
522 axis::draw_gridlines(
523 xs,
524 ys,
525 built.coord.as_ref(),
526 theme,
527 &panel_rect,
528 backend,
529 )?;
530 }
531
532 if panel.row == nrow - 1 || pi + ncol >= built.panels.len() {
534 axis::draw_x_axis(xs, built.coord.as_ref(), theme, &panel_rect, backend)?;
535 }
536
537 if panel.col == 0 {
539 axis::draw_y_axis(ys, built.coord.as_ref(), theme, &panel_rect, backend)?;
540 }
541 }
542
543 if pi < built.panels_data.len() {
545 for (li, layer_data) in built.panels_data[pi].iter().enumerate() {
546 if li < built.layers.len() && layer_data.nrows() > 0 {
547 let mut panel_backend = PanelBackendAdapter {
548 inner: backend,
549 panel_rect: panel_rect.clone(),
550 };
551 built.layers[li].geom.draw(
552 layer_data,
553 built.coord.as_ref(),
554 panel_scale_set,
555 theme,
556 &mut panel_backend,
557 )?;
558 }
559 }
560 }
561 }
562
563 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
565 if let Some(ref title) = built.labels.title {
566 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
567 let title_y = plot_area.y - theme.title.size * 0.9;
568 let family = if theme.title.family.is_empty() {
569 None
570 } else {
571 Some(theme.title.family.clone())
572 };
573 backend.draw_text(
574 title,
575 (tx, title_y.max(theme.title.size)),
576 &TextStyle {
577 color: theme.title.color,
578 size: theme.title.size,
579 anchor,
580 angle: 0.0,
581 family,
582 face: theme.title.face,
583 },
584 )?;
585 }
586
587 if let Some(ref subtitle) = built.labels.subtitle {
589 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
590 let subtitle_y = plot_area.y - 2.0;
591 let family = if theme.subtitle.family.is_empty() {
592 None
593 } else {
594 Some(theme.subtitle.family.clone())
595 };
596 backend.draw_text(
597 subtitle,
598 (sx, subtitle_y.max(theme.title.size + theme.subtitle.size)),
599 &TextStyle {
600 color: theme.subtitle.color,
601 size: theme.subtitle.size,
602 anchor,
603 angle: 0.0,
604 family,
605 face: theme.subtitle.face,
606 },
607 )?;
608 }
609
610 if let Some(ref caption) = built.labels.caption {
612 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
613 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
614 let family = if theme.caption.family.is_empty() {
615 None
616 } else {
617 Some(theme.caption.family.clone())
618 };
619 backend.draw_text(
620 caption,
621 (cx, caption_y),
622 &TextStyle {
623 color: theme.caption.color,
624 size: theme.caption.size,
625 anchor,
626 angle: 0.0,
627 family,
628 face: theme.caption.face,
629 },
630 )?;
631 }
632
633 Self::draw_annotations(
635 &built.annotations,
636 &built.scales,
637 built.coord.as_ref(),
638 &plot_area,
639 backend,
640 )?;
641
642 legend::draw_legend(
644 &built.scales,
645 theme,
646 &plot_area,
647 backend,
648 &built.guide_legend,
649 &built.suppressed_aes,
650 )?;
651
652 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
654
655 Ok(())
656 }
657
658 fn draw_annotations(
659 annotations: &[Annotation],
660 scales: &crate::scale::ScaleSet,
661 coord: &dyn crate::coord::Coord,
662 plot_area: &crate::render::Rect,
663 backend: &mut dyn DrawBackend,
664 ) -> Result<(), RenderError> {
665 use crate::data::Value;
666
667 let x_scale = scales.get(&Aesthetic::X);
668 let y_scale = scales.get(&Aesthetic::Y);
669
670 for ann in annotations {
671 match ann {
672 Annotation::Text {
673 label,
674 x,
675 y,
676 size,
677 color,
678 } => {
679 let nx = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
680 let ny = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
681 let pos = coord.transform((nx, ny), plot_area);
682 backend.draw_text(
683 label,
684 pos,
685 &TextStyle {
686 color: *color,
687 size: *size,
688 anchor: TextAnchor::Middle,
689 angle: 0.0,
690 family: None,
691 face: crate::render::backend::FontFace::Plain,
692 },
693 )?;
694 }
695 Annotation::Rect {
696 xmin,
697 xmax,
698 ymin,
699 ymax,
700 fill,
701 alpha,
702 } => {
703 let nx0 = x_scale.map(|s| s.map(&Value::Float(*xmin))).unwrap_or(0.0);
704 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xmax))).unwrap_or(1.0);
705 let ny0 = y_scale.map(|s| s.map(&Value::Float(*ymin))).unwrap_or(0.0);
706 let ny1 = y_scale.map(|s| s.map(&Value::Float(*ymax))).unwrap_or(1.0);
707 let tl = coord.transform((nx0, ny1), plot_area);
708 let br = coord.transform((nx1, ny0), plot_area);
709 backend.draw_rect(
710 tl,
711 br,
712 &RectStyle {
713 fill: Some(*fill),
714 stroke: None,
715 stroke_width: 0.0,
716 alpha: *alpha,
717 clip: false,
718 },
719 )?;
720 }
721 Annotation::Segment {
722 x,
723 y,
724 xend,
725 yend,
726 color,
727 width,
728 } => {
729 let nx0 = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
730 let ny0 = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
731 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xend))).unwrap_or(1.0);
732 let ny1 = y_scale.map(|s| s.map(&Value::Float(*yend))).unwrap_or(1.0);
733 let p0 = coord.transform((nx0, ny0), plot_area);
734 let p1 = coord.transform((nx1, ny1), plot_area);
735 backend.draw_line(
736 &[p0, p1],
737 &LineStyle {
738 color: *color,
739 alpha: 1.0,
740 width: *width,
741 linetype: Linetype::Solid,
742 },
743 )?;
744 }
745 }
746 }
747 Ok(())
748 }
749}
750
751struct PanelBackendAdapter<'a> {
753 inner: &'a mut dyn DrawBackend,
754 panel_rect: crate::render::Rect,
755}
756
757impl<'a> DrawBackend for PanelBackendAdapter<'a> {
758 fn draw_circle(
759 &mut self,
760 center: (f64, f64),
761 radius: f64,
762 style: &crate::render::backend::PointStyle,
763 ) -> Result<(), RenderError> {
764 self.inner.draw_circle(center, radius, style)
765 }
766 fn draw_line(
767 &mut self,
768 points: &[(f64, f64)],
769 style: &crate::render::backend::LineStyle,
770 ) -> Result<(), RenderError> {
771 self.inner.draw_line(points, style)
772 }
773 fn draw_rect(
774 &mut self,
775 top_left: (f64, f64),
776 bottom_right: (f64, f64),
777 style: &RectStyle,
778 ) -> Result<(), RenderError> {
779 self.inner.draw_rect(top_left, bottom_right, style)
780 }
781 fn draw_text(
782 &mut self,
783 text: &str,
784 pos: (f64, f64),
785 style: &TextStyle,
786 ) -> Result<(), RenderError> {
787 self.inner.draw_text(text, pos, style)
788 }
789 fn draw_polygon(
790 &mut self,
791 points: &[(f64, f64)],
792 style: &RectStyle,
793 ) -> Result<(), RenderError> {
794 self.inner.draw_polygon(points, style)
795 }
796 fn draw_shape(
797 &mut self,
798 center: (f64, f64),
799 radius: f64,
800 style: &crate::render::backend::PointStyle,
801 ) -> Result<(), RenderError> {
802 self.inner.draw_shape(center, radius, style)
803 }
804 fn plot_area(&self) -> crate::render::Rect {
805 self.panel_rect.clone()
806 }
807 fn total_area(&self) -> crate::render::Rect {
808 self.inner.total_area()
809 }
810}