use serde::{Deserialize, Serialize};
use crate::axis::ticker::TickPrune;
#[cfg(feature = "portable")]
use crate::axis::axis::AxisSide;
#[cfg(feature = "portable")]
use crate::core::color::Rgba;
#[cfg(feature = "portable")]
use crate::core::{Bbox, RcParams};
#[cfg(feature = "portable")]
use crate::figure::axes::{Annotation, SecondaryXAxis, SpanLine, SpanRect};
#[cfg(feature = "portable")]
use crate::figure::colorbar::FigureColorbar;
#[cfg(feature = "portable")]
use crate::figure::legend::{ArtistKind, LegendLocation};
#[cfg(feature = "portable")]
use crate::figure::plotting_contour::ContourLabelCandidate;
#[cfg(feature = "portable")]
use crate::figure::text::Text;
#[cfg(feature = "portable")]
use crate::render::{CapStyle, JoinStyle};
#[cfg(feature = "portable")]
use super::data::{Accessor, ArrF64, ArrU8};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct PortableSpec {
pub(crate) schema: u32,
pub(crate) generator: crate::portable::GeneratorRef,
pub(crate) renderer: crate::portable::RendererRef,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) meta: Option<crate::portable::Meta>,
pub(crate) figure: FigureSpec,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) timeline: Option<crate::portable::Timeline>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) controls: Vec<crate::portable::Control>,
pub(crate) accessors: Vec<Accessor>,
}
#[cfg(feature = "portable")]
pub(crate) fn uses_axis_styling(figure: &FigureSpec) -> bool {
figure.axes.iter().any(|axes| {
axes.title_style.is_some() || axes.xaxis.minor.is_some() || axes.yaxis.minor.is_some()
})
}
#[cfg(feature = "portable")]
pub(crate) fn uses_text_sizes(figure: &FigureSpec) -> bool {
figure.suptitle_size.is_some() || figure.axes.iter().any(|axes| axes.text_size.is_some())
}
#[cfg(feature = "portable")]
pub(crate) fn uses_legend_markers(figure: &FigureSpec) -> bool {
figure.axes.iter().any(|axes| {
axes.artist_order.is_some() || axes.legend.iter().any(|entry| entry.marker.is_some())
})
}
#[cfg(feature = "portable")]
pub(crate) fn uses_figure_text(figure: &FigureSpec) -> bool {
!figure.texts.is_empty() || figure.supxlabel.is_some() || figure.supylabel.is_some()
}
#[cfg(feature = "portable")]
pub(crate) fn uses_image_norms(figure: &FigureSpec) -> bool {
figure.axes.iter().any(|axes| {
axes.images.iter().any(|image| {
image.norm.is_some()
|| image.bad.is_some()
|| image.over.is_some()
|| image.under.is_some()
})
})
}
#[cfg(feature = "portable")]
pub(crate) fn uses_colorbars(figure: &FigureSpec) -> bool {
figure.axes.iter().any(|axes| {
axes.colorbar.is_some()
|| axes.meshes.iter().any(|m| {
m.cmap.is_some()
|| m.norm.is_some()
|| m.bad.is_some()
|| m.over.is_some()
|| m.under.is_some()
})
|| axes.collections.iter().any(|c| {
c.cmap.is_some()
|| c.norm.is_some()
|| c.bad.is_some()
|| c.over.is_some()
|| c.under.is_some()
})
})
}
#[cfg(feature = "portable")]
pub(crate) fn uses_path_simplify(figure: &FigureSpec) -> bool {
let default = RcParams::default();
figure.rc.path_simplify != default.path_simplify
|| figure.rc.path_simplify_threshold != default.path_simplify_threshold
}
#[cfg(feature = "portable")]
pub(crate) fn uses_line_markers(figure: &FigureSpec) -> bool {
figure.axes.iter().any(|axes| {
axes.lines.iter().any(|l| l.marker.is_some())
|| axes.legend.iter().any(|e| e.line_marker.is_some())
})
}
#[cfg(feature = "portable")]
pub(crate) fn uses_line_styles(figure: &FigureSpec) -> bool {
let rc = &figure.rc;
let defaults = crate::artist::line::LineStyles::default();
crate::artist::line::LineStyles::from_rc(rc) != defaults
|| figure.axes.iter().any(|axes| {
axes.lines
.iter()
.any(|l| l.dash_cap.is_some() || l.dash_join.is_some())
})
}
fn is_zero_i32(value: &i32) -> bool {
*value == 0
}
#[cfg(feature = "portable")]
pub(crate) fn uses_log_ticks(figure: &FigureSpec) -> bool {
let log_ticks = |axis: &AxisSpec| {
let locators = std::iter::once(&axis.locator)
.chain(axis.minor.as_ref().and_then(|m| m.locator.as_ref()));
locators
.into_iter()
.any(|l| matches!(l, LocatorSpec::Log { auto: true, .. }))
|| matches!(
axis.formatter,
FormatterSpec::LogMathtext { min_exponent, .. } if min_exponent != 0
)
};
figure.rc.axes_formatter_min_exponent != 0
|| figure
.axes
.iter()
.any(|axes| log_ticks(&axes.xaxis) || log_ticks(&axes.yaxis))
}
#[cfg(feature = "portable")]
fn round_join() -> crate::render::JoinStyle {
crate::render::JoinStyle::Round
}
#[cfg(feature = "portable")]
fn is_round_join(join: &crate::render::JoinStyle) -> bool {
*join == crate::render::JoinStyle::Round
}
#[cfg(feature = "portable")]
pub(crate) fn uses_collection_joins(figure: &FigureSpec) -> bool {
figure.axes.iter().any(|axes| {
axes.collections
.iter()
.any(|c| c.join != crate::render::JoinStyle::Round)
})
}
#[cfg(feature = "portable")]
fn is_line_axisbelow(axisbelow: &crate::core::rcparams::AxisBelow) -> bool {
*axisbelow == crate::core::rcparams::AxisBelow::Line
}
#[cfg(feature = "portable")]
pub(crate) fn uses_axisbelow(figure: &FigureSpec) -> bool {
use crate::core::rcparams::AxisBelow;
figure.rc.axes_axisbelow != AxisBelow::Line
|| figure
.axes
.iter()
.any(|axes| axes.axisbelow != AxisBelow::Line)
}
#[cfg(feature = "portable")]
pub(crate) fn uses_sky_projection(figure: &FigureSpec) -> bool {
figure.axes.iter().any(|axes| axes.sky.is_some())
}
#[cfg(feature = "portable")]
pub(crate) fn uses_image_normalize(figure: &FigureSpec) -> bool {
use crate::core::color::Norm;
let astro = |norm: &Option<Norm>| matches!(norm, Some(Norm::ImageNormalize(_)));
figure.axes.iter().any(|axes| {
axes.images.iter().any(|i| astro(&i.norm))
|| axes.meshes.iter().any(|m| astro(&m.norm))
|| axes.collections.iter().any(|c| astro(&c.norm))
})
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct FigureSpec {
pub(crate) width_in: f64,
pub(crate) height_in: f64,
pub(crate) dpi: f64,
pub(crate) facecolor: Rgba,
pub(crate) rc: RcParams,
pub(crate) suptitle: Option<String>,
pub(crate) suptitle_color: Rgba,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) suptitle_size: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) supxlabel: Option<Text>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) supylabel: Option<Text>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) texts: Vec<Text>,
pub(crate) axes: Vec<AxesSpec>,
pub(crate) colorbars: Vec<FigureColorbar>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct AxesSpec {
pub(crate) position: Bbox,
pub(crate) layout_envelope: Option<Bbox>,
pub(crate) sticky_x: Vec<f64>,
pub(crate) sticky_y: Vec<f64>,
pub(crate) xlim: Option<(f64, f64)>,
pub(crate) ylim: Option<(f64, f64)>,
pub(crate) xinverted: bool,
pub(crate) yinverted: bool,
pub(crate) margins: f64,
pub(crate) xscale: ScaleWire,
pub(crate) yscale: ScaleWire,
pub(crate) facecolor: Rgba,
pub(crate) edgecolor: Rgba,
pub(crate) linewidth: f64,
pub(crate) title_color: Rgba,
pub(crate) prop_cycle: Vec<Rgba>,
pub(crate) prop_cycle_index: usize,
pub(crate) legend_facecolor: Rgba,
pub(crate) legend_edgecolor: Rgba,
pub(crate) legend_labelcolor: Rgba,
pub(crate) legend_title: Option<String>,
pub(crate) legend_location: LegendLocation,
pub(crate) lines: Vec<LineSpec>,
pub(crate) patches: Vec<PatchSpec>,
pub(crate) collections: Vec<CollectionSpec>,
pub(crate) images: Vec<ImageSpec>,
pub(crate) meshes: Vec<QuadMeshSpec>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) poly_collections: Vec<PolyCollectionSpec>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) quivers: Vec<QuiverSpec>,
pub(crate) extra_data_bbox: Option<Bbox>,
pub(crate) xaxis: AxisSpec,
pub(crate) yaxis: AxisSpec,
pub(crate) xaxis_hidden: bool,
pub(crate) xlim_link: Option<usize>,
pub(crate) secondary_x: Option<SecondaryXAxis>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) colorbar: Option<crate::figure::Colorbar>,
pub(crate) title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) title_style: Option<(f64, f64)>,
pub(crate) annotations: Vec<Annotation>,
pub(crate) annotation_color: Rgba,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) text_size: Option<f64>,
pub(crate) contour_label_candidates: Vec<ContourLabelCandidate>,
pub(crate) frame: bool,
pub(crate) aspect_equal: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) sky: Option<crate::figure::sky::SkyProjection>,
#[serde(default, skip_serializing_if = "is_line_axisbelow")]
pub(crate) axisbelow: crate::core::rcparams::AxisBelow,
pub(crate) axis_visible: bool,
pub(crate) scope: bool,
pub(crate) span_lines: Vec<SpanLine>,
pub(crate) span_rects: Vec<SpanRect>,
pub(crate) legend: Vec<LegendEntrySpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) artist_order: Option<Vec<ArtistKind>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct LegendEntrySpec {
pub(crate) color: Rgba,
pub(crate) label: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) marker: Option<CollectionSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) line_marker: Option<crate::artist::line::LineMarker>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct AxisSpec {
pub(crate) side: AxisSide,
pub(crate) scale: ScaleWire,
pub(crate) locator: LocatorSpec,
pub(crate) formatter: FormatterSpec,
pub(crate) label: Option<String>,
pub(crate) color: Rgba,
pub(crate) tick_length: f64,
pub(crate) tick_width: f64,
pub(crate) tick_label_size: f64,
pub(crate) axis_label_size: f64,
pub(crate) tick_label_pad: f64,
pub(crate) axis_label_pad: f64,
pub(crate) grid: bool,
pub(crate) grid_color: Rgba,
pub(crate) grid_linewidth: f64,
pub(crate) grid_alpha: f64,
pub(crate) tick_direction: crate::core::rcparams::TickDirection,
pub(crate) tick_labels_visible: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) minor: Option<MinorAxisSpec>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct MinorAxisSpec {
pub(crate) locator: Option<LocatorSpec>,
pub(crate) tick_length: f64,
pub(crate) tick_width: f64,
pub(crate) grid: bool,
pub(crate) grid_color: Rgba,
pub(crate) grid_linewidth: f64,
pub(crate) grid_alpha: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct LineSpec {
pub(crate) x: ArrF64,
pub(crate) y: ArrF64,
pub(crate) color: Rgba,
pub(crate) linewidth: f64,
pub(crate) dashes: Option<(f64, Vec<f64>)>,
pub(crate) cap: CapStyle,
pub(crate) join: JoinStyle,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) dash_cap: Option<CapStyle>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) dash_join: Option<JoinStyle>,
pub(crate) visible: bool,
pub(crate) zorder: f64,
pub(crate) label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) marker: Option<crate::artist::line::LineMarker>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct PatchSpec {
pub(crate) path: PathSpec,
pub(crate) facecolor: Option<Rgba>,
pub(crate) edgecolor: Option<Rgba>,
pub(crate) linewidth: f64,
pub(crate) dashes: Option<(f64, Vec<f64>)>,
pub(crate) cap: CapStyle,
pub(crate) join: JoinStyle,
pub(crate) visible: bool,
pub(crate) zorder: f64,
pub(crate) label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct CollectionSpec {
pub(crate) offsets: ArrF64,
pub(crate) marker: PathSpec,
pub(crate) sizes: ArrF64,
pub(crate) facecolors: Vec<Rgba>,
pub(crate) edgecolors: Vec<Rgba>,
pub(crate) linewidth: f64,
#[serde(default = "round_join", skip_serializing_if = "is_round_join")]
pub(crate) join: crate::render::JoinStyle,
pub(crate) visible: bool,
pub(crate) zorder: f64,
pub(crate) label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) cmap: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) norm: Option<crate::core::color::Norm>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) bad: Option<Rgba>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) over: Option<Rgba>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) under: Option<Rgba>,
}
#[cfg(feature = "portable")]
pub(crate) struct FlatScale {
pub(crate) cmap: Option<String>,
pub(crate) norm: Option<crate::core::color::Norm>,
pub(crate) bad: Option<Rgba>,
pub(crate) over: Option<Rgba>,
pub(crate) under: Option<Rgba>,
}
#[cfg(feature = "portable")]
impl FlatScale {
pub(crate) fn of(scale: Option<&crate::core::color::ColorScale>) -> Self {
FlatScale {
cmap: scale.map(|s| s.cmap.to_wire()),
norm: scale.map(|s| s.norm.clone()),
bad: scale.and_then(|s| s.extremes.bad),
over: scale.and_then(|s| s.extremes.over),
under: scale.and_then(|s| s.extremes.under),
}
}
pub(crate) fn into_scale(
self,
) -> Result<Option<crate::core::color::ColorScale>, crate::portable::PortableError> {
let extremes = crate::core::color::Extremes {
bad: self.bad,
over: self.over,
under: self.under,
};
match (self.cmap, self.norm) {
(Some(cmap), Some(norm)) => Ok(Some(crate::core::color::ColorScale {
cmap: crate::core::color::CmapSpec::from_wire(&cmap)?,
norm,
extremes,
})),
(None, None) if extremes == crate::core::color::Extremes::default() => Ok(None),
_ => Err(crate::portable::PortableError::Malformed(
"a color scale needs both a colormap and a norm".to_owned(),
)),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct ImageSpec {
pub(crate) data: ArrF64,
pub(crate) nrows: usize,
pub(crate) ncols: usize,
pub(crate) extent: [f64; 4],
pub(crate) vmin: f64,
pub(crate) vmax: f64,
pub(crate) cmap: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) norm: Option<crate::core::color::Norm>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) bad: Option<Rgba>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) over: Option<Rgba>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) under: Option<Rgba>,
pub(crate) origin_upper: bool,
pub(crate) zorder: f64,
pub(crate) visible: bool,
pub(crate) label: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct QuadMeshSpec {
pub(crate) nrows: usize,
pub(crate) ncols: usize,
pub(crate) coordinates: ArrF64,
pub(crate) facecolors: Vec<Rgba>,
pub(crate) vertex_colors: Option<Vec<Rgba>>,
pub(crate) edgecolor: Option<Rgba>,
pub(crate) linewidth: f64,
pub(crate) zorder: f64,
pub(crate) visible: bool,
pub(crate) label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) cmap: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) norm: Option<crate::core::color::Norm>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) bad: Option<Rgba>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) over: Option<Rgba>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) under: Option<Rgba>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct QuiverSpec {
pub(crate) x: ArrF64,
pub(crate) y: ArrF64,
pub(crate) u: ArrF64,
pub(crate) v: ArrF64,
pub(crate) options: crate::figure::QuiverOptions,
pub(crate) zorder: f64,
pub(crate) visible: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct PolyCollectionSpec {
pub(crate) vertices: ArrF64,
pub(crate) facecolors: Vec<Rgba>,
pub(crate) edgecolor: Option<Rgba>,
pub(crate) linewidth: f64,
pub(crate) zorder: f64,
pub(crate) visible: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) cmap: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) norm: Option<crate::core::color::Norm>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) bad: Option<Rgba>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) over: Option<Rgba>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) under: Option<Rgba>,
}
#[cfg(feature = "portable")]
pub(crate) fn uses_embedded_colormaps(figure: &FigureSpec) -> bool {
use crate::core::color::CmapSpec;
let embedded = |cmap: &Option<String>| cmap.as_deref().is_some_and(CmapSpec::is_embedded_wire);
figure.axes.iter().any(|axes| {
axes.images
.iter()
.any(|i| CmapSpec::is_embedded_wire(&i.cmap))
|| axes.meshes.iter().any(|m| embedded(&m.cmap))
|| axes.collections.iter().any(|c| embedded(&c.cmap))
|| axes.poly_collections.iter().any(|p| embedded(&p.cmap))
})
}
#[cfg(feature = "portable")]
pub(crate) fn uses_poly_collections(figure: &FigureSpec) -> bool {
figure
.axes
.iter()
.any(|axes| !axes.poly_collections.is_empty())
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg(feature = "portable")]
pub(crate) struct PathSpec {
pub(crate) verts: ArrF64,
pub(crate) codes: Option<ArrU8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub(crate) enum ScaleWire {
Linear,
Log {
base: f64,
},
Symlog {
base: f64,
linthresh: f64,
linscale: f64,
},
Logit,
Asinh {
linear_width: f64,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub(crate) enum LocatorSpec {
MaxN {
nbins: Option<usize>,
extended_steps: Vec<f64>,
integer: bool,
symmetric: bool,
min_n_ticks: usize,
prune: TickPrune,
},
Auto,
AutoMinor {
subdivisions: Option<usize>,
},
Multiple {
step: f64,
offset: f64,
},
LinearN {
numticks: usize,
},
Fixed {
locs: Vec<f64>,
nbins: Option<usize>,
},
Log {
base: f64,
subs: Vec<f64>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
auto: bool,
},
Symlog {
base: f64,
linthresh: f64,
linear_ticks: usize,
},
Asinh {
base: f64,
linear_width: f64,
linear_ticks: usize,
},
Logit {
max_exponent: i32,
},
Index {
base: f64,
offset: f64,
},
Null,
AutoDate {
maxticks: usize,
},
AutoDateMinor {
maxticks: usize,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub(crate) enum FormatterSpec {
Scalar {
decimals: Option<usize>,
},
Log {
base: f64,
},
LogMathtext {
base: f64,
#[serde(default, skip_serializing_if = "is_zero_i32")]
min_exponent: i32,
},
Symlog {
base: f64,
linthresh: f64,
},
SymlogMathtext {
base: f64,
linthresh: f64,
},
Asinh {
base: f64,
linear_width: f64,
},
AsinhMathtext {
base: f64,
linear_width: f64,
},
Logit {
max_exponent: i32,
},
LogitMathtext {
max_exponent: i32,
},
Eng {
unit: String,
places: Option<usize>,
separator: String,
},
Percent {
xmax: f64,
decimals: usize,
symbol: String,
},
Null,
Fixed {
seq: Vec<String>,
},
Index {
labels: Vec<String>,
},
FormatStr {
fmt: String,
},
StrMethod {
fmt: String,
},
Date {
fmt: String,
},
ConciseDate,
}