Skip to main content

ggplot_rs/theme/
mod.rs

1#[cfg(feature = "serde")]
2pub mod config;
3pub mod elements;
4pub mod presets;
5
6pub use elements::{ElementGeom, ElementLine, ElementRect, ElementText};
7
8/// Position of the legend.
9#[derive(Clone, Debug)]
10pub enum LegendPosition {
11    Right,
12    Left,
13    Top,
14    Bottom,
15    None,
16    /// Inside the panel at panel-relative coordinates (0..1, 0..1), like R's
17    /// `legend.position = c(x, y)`. `(0, 0)` is bottom-left, `(1, 1)` top-right.
18    Inside(f64, f64),
19}
20
21/// A panel corner for an inside legend
22/// ([`GGPlot::legend_inside_auto_with`](crate::GGPlot::legend_inside_auto_with)).
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum LegendCorner {
25    TopLeft,
26    TopRight,
27    BottomLeft,
28    BottomRight,
29}
30
31impl LegendCorner {
32    /// Every corner, in the default preference order.
33    pub const ALL: [LegendCorner; 4] = [
34        LegendCorner::BottomRight,
35        LegendCorner::BottomLeft,
36        LegendCorner::TopRight,
37        LegendCorner::TopLeft,
38    ];
39
40    /// The matching [`LegendPosition::Inside`] fractions.
41    pub fn inside(self) -> LegendPosition {
42        match self {
43            LegendCorner::TopLeft => LegendPosition::Inside(0.0, 1.0),
44            LegendCorner::TopRight => LegendPosition::Inside(1.0, 1.0),
45            LegendCorner::BottomLeft => LegendPosition::Inside(0.0, 0.0),
46            LegendCorner::BottomRight => LegendPosition::Inside(1.0, 0.0),
47        }
48    }
49
50    /// `"top-left"` etc. (the `data-legend-at` value after `inside-`).
51    pub fn name(self) -> &'static str {
52        match self {
53            LegendCorner::TopLeft => "top-left",
54            LegendCorner::TopRight => "top-right",
55            LegendCorner::BottomLeft => "bottom-left",
56            LegendCorner::BottomRight => "bottom-right",
57        }
58    }
59}
60
61/// Whether title/subtitle/caption align to the panel or the whole plot width
62/// (R's `plot.title.position` / `plot.caption.position`).
63#[derive(Clone, Copy, Debug, Default, PartialEq)]
64pub enum TitlePosition {
65    #[default]
66    Panel,
67    Plot,
68}
69
70/// Corner for the `labs(tag)` label (R's `plot.tag.position`).
71#[derive(Clone, Copy, Debug, Default, PartialEq)]
72pub enum TagPosition {
73    #[default]
74    TopLeft,
75    TopRight,
76    BottomLeft,
77    BottomRight,
78}
79
80/// Layout direction of the legend keys (R's `legend.direction`).
81#[derive(Clone, Copy, Debug, PartialEq)]
82pub enum LegendDirection {
83    Vertical,
84    Horizontal,
85}
86
87/// How an outer x-axis tick label that would cross the SVG's edge is
88/// handled (B3-1; ggplot2 itself leaves this to the viewport/device and has
89/// no direct equivalent — this is a ggplot-rs extension, D4).
90#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
91pub enum AxisLabelFit {
92    /// The 0.21 behaviour: the label is drawn exactly where ggplot2's own
93    /// math puts it, even if that crosses the SVG edge.
94    Clip,
95    /// The panel keeps its size; an outer label that would cross the edge
96    /// is shifted inward just enough to stay inside, at draw time. Use this
97    /// for panels that must stay pixel-aligned with other elements (e.g. a
98    /// consumer's `::TRACK`-aligned strip).
99    Shift,
100    /// The layout reserves just the overflow as extra left/right panel
101    /// margin, so a plot whose outer labels already fit is byte-identical.
102    /// Anything still outside after that (e.g. a faceted plot, where the
103    /// reservation is skipped) is shifted inward at draw time, same as
104    /// [`Shift`](Self::Shift).
105    #[default]
106    Reserve,
107}
108
109/// Plot margins (in pixels).
110#[derive(Clone, Debug)]
111pub struct Margin {
112    pub top: f64,
113    pub right: f64,
114    pub bottom: f64,
115    pub left: f64,
116}
117
118impl Default for Margin {
119    fn default() -> Self {
120        Margin {
121            top: 10.0,
122            right: 10.0,
123            bottom: 10.0,
124            left: 10.0,
125        }
126    }
127}
128
129/// Complete theme specification for a plot.
130#[derive(Clone, Debug)]
131pub struct Theme {
132    // ── Existing fields ──
133    pub text: ElementText,
134    pub title: ElementText,
135    pub axis_text_x: ElementText,
136    pub axis_text_y: ElementText,
137    pub axis_title_x: ElementText,
138    pub axis_title_y: ElementText,
139    pub axis_line: ElementLine,
140    pub axis_ticks: ElementLine,
141    pub panel_background: ElementRect,
142    pub panel_grid_major: ElementLine,
143    pub panel_grid_minor: ElementLine,
144    pub plot_background: ElementRect,
145    pub legend_position: LegendPosition,
146    pub plot_margin: Margin,
147
148    // ── New text elements ──
149    pub subtitle: ElementText,
150    pub caption: ElementText,
151    pub legend_title: ElementText,
152    pub legend_text: ElementText,
153    pub strip_text: ElementText,
154
155    // ── Per-axis optional overrides (None = inherit from parent) ──
156    pub axis_line_x: Option<ElementLine>,
157    pub axis_line_y: Option<ElementLine>,
158    pub axis_ticks_x: Option<ElementLine>,
159    pub axis_ticks_y: Option<ElementLine>,
160    pub panel_grid_major_x: Option<ElementLine>,
161    pub panel_grid_major_y: Option<ElementLine>,
162    pub panel_grid_minor_x: Option<ElementLine>,
163    pub panel_grid_minor_y: Option<ElementLine>,
164
165    // ── New rect/line elements ──
166    pub panel_border: ElementLine,
167    pub legend_background: ElementRect,
168    pub legend_key: ElementRect,
169    pub strip_background: ElementRect,
170
171    // ── Scalar spacing/sizing ──
172    pub axis_ticks_length: f64,
173    /// Number of rows to stagger x-axis tick labels across (R's
174    /// `guide_axis(n.dodge = ...)`); 1 = no dodging.
175    pub axis_text_x_dodge: usize,
176    pub legend_key_width: f64,
177    pub legend_key_height: f64,
178    pub legend_spacing: f64,
179    pub legend_margin: Margin,
180    pub panel_spacing: f64,
181    pub panel_spacing_x: Option<f64>,
182    pub panel_spacing_y: Option<f64>,
183
184    // ── Brand / primary color ──
185    /// Optional brand color. When set, geoms that draw a single un-mapped series
186    /// (no color/fill aesthetic) use it as their default instead of the geom's
187    /// built-in color. Lets one render process serve multiple tenants' brands.
188    pub primary: Option<(u8, u8, u8)>,
189
190    // ── Advanced layout (R's theme() extras) ──
191    /// Fix the panel's height:width ratio (R's `aspect.ratio`). `None` = free.
192    pub aspect_ratio: Option<f64>,
193    /// Draw gridlines on top of the data layers (R's `panel.ontop`).
194    pub panel_ontop: bool,
195    /// Draw minor tick marks between major ticks (R's `axis.minor.ticks`).
196    pub axis_minor_ticks: bool,
197    /// Whether title/subtitle/caption align to the panel or the whole plot
198    /// (R's `plot.title.position` / `plot.caption.position`).
199    pub title_position: TitlePosition,
200    /// Corner for the `labs(tag)` label (R's `plot.tag.position`).
201    pub tag_position: TagPosition,
202    /// Legend key layout direction; `None` = auto from `legend_position`.
203    pub legend_direction: Option<LegendDirection>,
204    /// Month outlines of `geom_calendar`; `ElementLine::blank()` hides them.
205    pub calendar_month_border: ElementLine,
206    /// Frame around a continuous colourbar. `None` keeps the default
207    /// (`legend_key.color`, else dark grey, 0.5 wide); `ElementLine::blank()`
208    /// hides it.
209    pub legend_bar_border: Option<ElementLine>,
210    /// Geom ink/paper/accent/family palette (ggplot2 4.0's `element_geom`,
211    /// B2-7). Every field `None` (the default) changes nothing.
212    pub geom: ElementGeom,
213
214    /// How an outer x-axis label that would cross the SVG edge is handled
215    /// (B3-1, D6). Default [`AxisLabelFit::Reserve`].
216    pub axis_text_x_fit: AxisLabelFit,
217    /// Thin crowded x-axis labels in ggplot2's `check.overlap` priority
218    /// order (B3-1, D5): off by default, so default output is unaffected.
219    pub axis_text_x_check_overlap: bool,
220}
221
222impl Theme {
223    /// Hide only the y position guides (axis text, title, line, ticks and
224    /// the horizontal grid lines) — layout charts with a real x axis but a
225    /// meaningless y (`geom_timeline`).
226    pub fn hide_y_guides(&mut self) {
227        self.axis_text_y.visible = false;
228        self.axis_title_y.visible = false;
229        let hidden = |base: &ElementLine| ElementLine {
230            visible: false,
231            ..base.clone()
232        };
233        self.axis_line_y = Some(hidden(self.axis_line_y.as_ref().unwrap_or(&self.axis_line)));
234        self.axis_ticks_y = Some(hidden(
235            self.axis_ticks_y.as_ref().unwrap_or(&self.axis_ticks),
236        ));
237        self.panel_grid_major_y = Some(hidden(
238            self.panel_grid_major_y
239                .as_ref()
240                .unwrap_or(&self.panel_grid_major),
241        ));
242        self.panel_grid_minor_y = Some(hidden(
243            self.panel_grid_minor_y
244                .as_ref()
245                .unwrap_or(&self.panel_grid_minor),
246        ));
247    }
248
249    /// Hide the position guides — axis text, titles, lines, ticks and panel
250    /// grid — for layout-only charts whose x/y carry no data meaning
251    /// (`geom_sankey`). Backgrounds, legend and titles are untouched.
252    pub fn hide_position_guides(&mut self) {
253        for t in [
254            &mut self.axis_text_x,
255            &mut self.axis_text_y,
256            &mut self.axis_title_x,
257            &mut self.axis_title_y,
258        ] {
259            t.visible = false;
260        }
261        for l in [
262            &mut self.axis_line,
263            &mut self.axis_ticks,
264            &mut self.panel_grid_major,
265            &mut self.panel_grid_minor,
266        ] {
267            l.visible = false;
268        }
269        for l in [
270            &mut self.axis_line_x,
271            &mut self.axis_line_y,
272            &mut self.axis_ticks_x,
273            &mut self.axis_ticks_y,
274            &mut self.panel_grid_major_x,
275            &mut self.panel_grid_major_y,
276            &mut self.panel_grid_minor_x,
277            &mut self.panel_grid_minor_y,
278        ]
279        .into_iter()
280        .flatten()
281        {
282            l.visible = false;
283        }
284    }
285
286    /// Resolve the effective series color: the theme's brand color if set,
287    /// otherwise the geom's own default.
288    pub fn primary_or(&self, fallback: (u8, u8, u8)) -> (u8, u8, u8) {
289        self.primary.unwrap_or(fallback)
290    }
291
292    /// Set the brand/primary color (builder style).
293    pub fn with_primary(mut self, color: (u8, u8, u8)) -> Self {
294        self.primary = Some(color);
295        self
296    }
297
298    /// Set the geom ink/paper/accent/family palette (B2-7).
299    pub fn set_geom(mut self, el: ElementGeom) -> Self {
300        self.geom = el;
301        self
302    }
303
304    /// Set just the geom ink and paper colours (B2-7's stretch goal),
305    /// leaving `accent`/`family` as they were.
306    pub fn with_ink_paper(mut self, ink: (u8, u8, u8), paper: (u8, u8, u8)) -> Self {
307        self.geom.ink = Some(ink);
308        self.geom.paper = Some(paper);
309        self
310    }
311
312    /// Resolve R-style element inheritance: text-child elements still at the
313    /// default `family`/`color` inherit them from the root `text` element, so
314    /// setting only `theme.text.family` restyles every text element. Child
315    /// elements that already override a property keep it. Called once before
316    /// rendering.
317    pub fn resolve_inheritance(&mut self) {
318        let root = self.text.clone();
319        let def = ElementText::default();
320        let children: [&mut ElementText; 10] = [
321            &mut self.title,
322            &mut self.subtitle,
323            &mut self.caption,
324            &mut self.axis_text_x,
325            &mut self.axis_text_y,
326            &mut self.axis_title_x,
327            &mut self.axis_title_y,
328            &mut self.legend_title,
329            &mut self.legend_text,
330            &mut self.strip_text,
331        ];
332        for el in children {
333            if el.family == def.family {
334                el.family = root.family.clone();
335            }
336            if el.color == def.color {
337                el.color = root.color;
338            }
339        }
340    }
341
342    // ── Fallback accessors for Option fields ──
343
344    pub fn get_axis_line_x(&self) -> &ElementLine {
345        self.axis_line_x.as_ref().unwrap_or(&self.axis_line)
346    }
347
348    pub fn get_axis_line_y(&self) -> &ElementLine {
349        self.axis_line_y.as_ref().unwrap_or(&self.axis_line)
350    }
351
352    pub fn get_axis_ticks_x(&self) -> &ElementLine {
353        self.axis_ticks_x.as_ref().unwrap_or(&self.axis_ticks)
354    }
355
356    pub fn get_axis_ticks_y(&self) -> &ElementLine {
357        self.axis_ticks_y.as_ref().unwrap_or(&self.axis_ticks)
358    }
359
360    pub fn get_panel_grid_major_x(&self) -> &ElementLine {
361        self.panel_grid_major_x
362            .as_ref()
363            .unwrap_or(&self.panel_grid_major)
364    }
365
366    pub fn get_panel_grid_major_y(&self) -> &ElementLine {
367        self.panel_grid_major_y
368            .as_ref()
369            .unwrap_or(&self.panel_grid_major)
370    }
371
372    pub fn get_panel_grid_minor_x(&self) -> &ElementLine {
373        self.panel_grid_minor_x
374            .as_ref()
375            .unwrap_or(&self.panel_grid_minor)
376    }
377
378    pub fn get_panel_grid_minor_y(&self) -> &ElementLine {
379        self.panel_grid_minor_y
380            .as_ref()
381            .unwrap_or(&self.panel_grid_minor)
382    }
383
384    pub fn get_panel_spacing_x(&self) -> f64 {
385        self.panel_spacing_x.unwrap_or(self.panel_spacing)
386    }
387
388    pub fn get_panel_spacing_y(&self) -> f64 {
389        self.panel_spacing_y.unwrap_or(self.panel_spacing)
390    }
391
392    // ── Existing setters ──
393
394    pub fn set_axis_text_x(mut self, el: ElementText) -> Self {
395        self.axis_text_x = el;
396        self
397    }
398
399    pub fn set_axis_text_y(mut self, el: ElementText) -> Self {
400        self.axis_text_y = el;
401        self
402    }
403
404    pub fn set_axis_title_x(mut self, el: ElementText) -> Self {
405        self.axis_title_x = el;
406        self
407    }
408
409    pub fn set_axis_title_y(mut self, el: ElementText) -> Self {
410        self.axis_title_y = el;
411        self
412    }
413
414    pub fn set_axis_line(mut self, el: ElementLine) -> Self {
415        self.axis_line = el;
416        self
417    }
418
419    pub fn set_axis_ticks(mut self, el: ElementLine) -> Self {
420        self.axis_ticks = el;
421        self
422    }
423
424    pub fn set_panel_background(mut self, el: ElementRect) -> Self {
425        self.panel_background = el;
426        self
427    }
428
429    pub fn set_panel_grid_major(mut self, el: ElementLine) -> Self {
430        self.panel_grid_major = el;
431        self
432    }
433
434    pub fn set_panel_grid_minor(mut self, el: ElementLine) -> Self {
435        self.panel_grid_minor = el;
436        self
437    }
438
439    pub fn set_plot_background(mut self, el: ElementRect) -> Self {
440        self.plot_background = el;
441        self
442    }
443
444    pub fn set_legend_position(mut self, pos: LegendPosition) -> Self {
445        self.legend_position = pos;
446        self
447    }
448
449    pub fn set_plot_margin(mut self, margin: Margin) -> Self {
450        self.plot_margin = margin;
451        self
452    }
453
454    pub fn set_title(mut self, el: ElementText) -> Self {
455        self.title = el;
456        self
457    }
458
459    pub fn set_text(mut self, el: ElementText) -> Self {
460        self.text = el;
461        self
462    }
463
464    // ── New setters ──
465
466    pub fn set_subtitle(mut self, el: ElementText) -> Self {
467        self.subtitle = el;
468        self
469    }
470
471    pub fn set_caption(mut self, el: ElementText) -> Self {
472        self.caption = el;
473        self
474    }
475
476    pub fn set_legend_title(mut self, el: ElementText) -> Self {
477        self.legend_title = el;
478        self
479    }
480
481    pub fn set_legend_text(mut self, el: ElementText) -> Self {
482        self.legend_text = el;
483        self
484    }
485
486    pub fn set_strip_text(mut self, el: ElementText) -> Self {
487        self.strip_text = el;
488        self
489    }
490
491    pub fn set_axis_line_x(mut self, el: Option<ElementLine>) -> Self {
492        self.axis_line_x = el;
493        self
494    }
495
496    pub fn set_axis_line_y(mut self, el: Option<ElementLine>) -> Self {
497        self.axis_line_y = el;
498        self
499    }
500
501    pub fn set_axis_ticks_x(mut self, el: Option<ElementLine>) -> Self {
502        self.axis_ticks_x = el;
503        self
504    }
505
506    pub fn set_axis_ticks_y(mut self, el: Option<ElementLine>) -> Self {
507        self.axis_ticks_y = el;
508        self
509    }
510
511    pub fn set_panel_grid_major_x(mut self, el: Option<ElementLine>) -> Self {
512        self.panel_grid_major_x = el;
513        self
514    }
515
516    pub fn set_panel_grid_major_y(mut self, el: Option<ElementLine>) -> Self {
517        self.panel_grid_major_y = el;
518        self
519    }
520
521    pub fn set_panel_grid_minor_x(mut self, el: Option<ElementLine>) -> Self {
522        self.panel_grid_minor_x = el;
523        self
524    }
525
526    pub fn set_panel_grid_minor_y(mut self, el: Option<ElementLine>) -> Self {
527        self.panel_grid_minor_y = el;
528        self
529    }
530
531    pub fn set_panel_border(mut self, el: ElementLine) -> Self {
532        self.panel_border = el;
533        self
534    }
535
536    /// The effective colourbar frame: [`legend_bar_border`](Self::legend_bar_border),
537    /// else `legend_key.color` (dark grey when unset), 0.5 wide.
538    pub fn legend_bar_border_line(&self) -> ElementLine {
539        self.legend_bar_border.clone().unwrap_or(ElementLine {
540            color: self.legend_key.color.unwrap_or((50, 50, 50)),
541            width: 0.5,
542            visible: true,
543            linetype: crate::render::backend::Linetype::Solid,
544        })
545    }
546
547    pub fn set_calendar_month_border(mut self, el: ElementLine) -> Self {
548        self.calendar_month_border = el;
549        self
550    }
551
552    pub fn set_legend_bar_border(mut self, el: Option<ElementLine>) -> Self {
553        self.legend_bar_border = el;
554        self
555    }
556
557    pub fn set_legend_background(mut self, el: ElementRect) -> Self {
558        self.legend_background = el;
559        self
560    }
561
562    pub fn set_legend_key(mut self, el: ElementRect) -> Self {
563        self.legend_key = el;
564        self
565    }
566
567    pub fn set_strip_background(mut self, el: ElementRect) -> Self {
568        self.strip_background = el;
569        self
570    }
571
572    pub fn set_axis_ticks_length(mut self, val: f64) -> Self {
573        self.axis_ticks_length = val;
574        self
575    }
576
577    pub fn set_legend_key_width(mut self, val: f64) -> Self {
578        self.legend_key_width = val;
579        self
580    }
581
582    pub fn set_legend_key_height(mut self, val: f64) -> Self {
583        self.legend_key_height = val;
584        self
585    }
586
587    pub fn set_legend_spacing(mut self, val: f64) -> Self {
588        self.legend_spacing = val;
589        self
590    }
591
592    pub fn set_legend_margin(mut self, margin: Margin) -> Self {
593        self.legend_margin = margin;
594        self
595    }
596
597    pub fn set_panel_spacing(mut self, val: f64) -> Self {
598        self.panel_spacing = val;
599        self
600    }
601
602    pub fn set_panel_spacing_x(mut self, val: Option<f64>) -> Self {
603        self.panel_spacing_x = val;
604        self
605    }
606
607    pub fn set_panel_spacing_y(mut self, val: Option<f64>) -> Self {
608        self.panel_spacing_y = val;
609        self
610    }
611
612    /// Apply incremental theme modifications.
613    /// Only fields that are `Some` in the update are applied.
614    pub fn update(mut self, upd: ThemeUpdate) -> Self {
615        if let Some(v) = upd.text {
616            self.text = v;
617        }
618        if let Some(v) = upd.title {
619            self.title = v;
620        }
621        if let Some(v) = upd.subtitle {
622            self.subtitle = v;
623        }
624        if let Some(v) = upd.caption {
625            self.caption = v;
626        }
627        if let Some(v) = upd.axis_text_x {
628            self.axis_text_x = v;
629        }
630        if let Some(v) = upd.axis_text_y {
631            self.axis_text_y = v;
632        }
633        if let Some(v) = upd.axis_title_x {
634            self.axis_title_x = v;
635        }
636        if let Some(v) = upd.axis_title_y {
637            self.axis_title_y = v;
638        }
639        if let Some(v) = upd.axis_line {
640            self.axis_line = v;
641        }
642        if let Some(v) = upd.axis_ticks {
643            self.axis_ticks = v;
644        }
645        if let Some(v) = upd.panel_background {
646            self.panel_background = v;
647        }
648        if let Some(v) = upd.panel_grid_major {
649            self.panel_grid_major = v;
650        }
651        if let Some(v) = upd.panel_grid_minor {
652            self.panel_grid_minor = v;
653        }
654        if let Some(v) = upd.panel_border {
655            self.panel_border = v;
656        }
657        if let Some(v) = upd.calendar_month_border {
658            self.calendar_month_border = v;
659        }
660        if let Some(v) = upd.legend_bar_border {
661            self.legend_bar_border = Some(v);
662        }
663        if let Some(v) = upd.plot_background {
664            self.plot_background = v;
665        }
666        if let Some(v) = upd.legend_position {
667            self.legend_position = v;
668        }
669        if let Some(v) = upd.legend_title {
670            self.legend_title = v;
671        }
672        if let Some(v) = upd.legend_text {
673            self.legend_text = v;
674        }
675        if let Some(v) = upd.legend_background {
676            self.legend_background = v;
677        }
678        if let Some(v) = upd.strip_text {
679            self.strip_text = v;
680        }
681        if let Some(v) = upd.strip_background {
682            self.strip_background = v;
683        }
684        if let Some(v) = upd.plot_margin {
685            self.plot_margin = v;
686        }
687        if let Some(v) = upd.geom {
688            self.geom = v;
689        }
690        self
691    }
692}
693
694/// Incremental theme modifications. All fields are optional — only `Some` values are applied.
695/// Like R's `theme(axis.text.x = element_text(...))`.
696#[derive(Clone, Debug, Default)]
697pub struct ThemeUpdate {
698    pub text: Option<ElementText>,
699    pub title: Option<ElementText>,
700    pub subtitle: Option<ElementText>,
701    pub caption: Option<ElementText>,
702    pub axis_text_x: Option<ElementText>,
703    pub axis_text_y: Option<ElementText>,
704    pub axis_title_x: Option<ElementText>,
705    pub axis_title_y: Option<ElementText>,
706    pub axis_line: Option<ElementLine>,
707    pub axis_ticks: Option<ElementLine>,
708    pub panel_background: Option<ElementRect>,
709    pub panel_grid_major: Option<ElementLine>,
710    pub panel_grid_minor: Option<ElementLine>,
711    pub panel_border: Option<ElementLine>,
712    pub calendar_month_border: Option<ElementLine>,
713    pub legend_bar_border: Option<ElementLine>,
714    pub plot_background: Option<ElementRect>,
715    pub legend_position: Option<LegendPosition>,
716    pub legend_title: Option<ElementText>,
717    pub legend_text: Option<ElementText>,
718    pub legend_background: Option<ElementRect>,
719    pub strip_text: Option<ElementText>,
720    pub strip_background: Option<ElementRect>,
721    pub plot_margin: Option<Margin>,
722    /// Geom ink/paper/accent/family palette (B2-7).
723    pub geom: Option<ElementGeom>,
724}
725
726impl Default for Theme {
727    fn default() -> Self {
728        presets::theme_gray()
729    }
730}