Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions examples/bode_rlc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ fn main() {
(100.0, "mag3", "phase3", "R = 100 Ω"),
];

// [&str; 1] converts to Text::Rich
let title = [concat!(
"Bode diagram of RLC circuit\n",
"[size=18;italic;font=serif]L = 0.1 mH / C = 1 µF[/size;italic;font]"
)];
// &[&str] converts to Text::Rich with one line per element
let title = &[
"Bode diagram of RLC circuit",
"[size=18;italic;font=serif]L = 0.1 mH / C = 1 µF[/size;italic;font]",
];

// magnitude X axis scale is taken from the phase X axis
// the reference uses the title given to the phase X axis
Expand Down
60 changes: 49 additions & 11 deletions src/des.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ pub enum Text {
/// The format string for the rich text, with optional classes
fmt: String,
/// The classes that can be used in the format string
classes: Vec<(String, text::TextModifiers<theme::Color>)>,
classes: Vec<(String, text::TextProps<theme::Color>)>,
},
}

impl Text {
pub(crate) fn to_rich_text(
&self,
base: text::props::TextProps<theme::Color>,
base: text::props::TextBaseProps<theme::Color>,
layout: text::rich::Layout,
db: &text::fontdb::Database,
) -> std::result::Result<text::RichText<theme::Color>, text::Error> {
Expand Down Expand Up @@ -81,17 +81,31 @@ impl From<&str> for Text {
}
}

impl From<[String; 1]> for Text {
fn from(arr: [String; 1]) -> Self {
let mut arr = arr;
let fmt = std::mem::take(&mut arr[0]);
impl From<&[String]> for Text {
fn from(arr: &[String]) -> Self {
let fmt = arr.join("\n");
Text::Rich(fmt)
}
}

impl From<[&str; 1]> for Text {
fn from(arr: [&str; 1]) -> Self {
Text::Rich(arr[0].to_string())
impl From<&[&str]> for Text {
fn from(arr: &[&str]) -> Self {
let fmt = arr.join("\n");
Text::Rich(fmt)
}
}

impl<const N: usize> From<&[String; N]> for Text {
fn from(arr: &[String; N]) -> Self {
let fmt = arr.join("\n");
Text::Rich(fmt)
}
}

impl<const N: usize> From<&[&str; N]> for Text {
fn from(arr: &[&str; N]) -> Self {
let fmt = arr.join("\n");
Text::Rich(fmt)
}
}

Expand All @@ -107,8 +121,32 @@ impl From<(&str,)> for Text {
}
}

impl From<(String, Vec<(String, text::TextModifiers<theme::Color>)>)> for Text {
fn from(tuple: (String, Vec<(String, text::TextModifiers<theme::Color>)>)) -> Self {
impl From<(String, String)> for Text {
fn from(tuple: (String, String)) -> Self {
Text::Rich(tuple.0 + "\n" + &tuple.1)
}
}

impl From<(&str, &str)> for Text {
fn from(tuple: (&str, &str)) -> Self {
Text::Rich(tuple.0.to_string() + "\n" + tuple.1)
}
}

impl From<(String, String, String)> for Text {
fn from(tuple: (String, String, String)) -> Self {
Text::Rich(tuple.0 + "\n" + &tuple.1 + "\n" + &tuple.2)
}
}

impl From<(&str, &str, &str)> for Text {
fn from(tuple: (&str, &str, &str)) -> Self {
Text::Rich(tuple.0.to_string() + "\n" + tuple.1 + "\n" + tuple.2)
}
}

impl From<(String, Vec<(String, text::TextProps<theme::Color>)>)> for Text {
fn from(tuple: (String, Vec<(String, text::TextProps<theme::Color>)>)) -> Self {
Text::RichWithClasses {
fmt: tuple.0,
classes: tuple.1,
Expand Down
17 changes: 7 additions & 10 deletions src/des/axis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ pub mod ticks {
pub struct Ticks {
locator: Locator,
formatter: Option<Formatter>,
txt_modifiers: text::TextModifiers<theme::Color>,
txt_props: text::TextProps<theme::Color>,
color: theme::Color,
}

Expand All @@ -720,7 +720,7 @@ pub mod ticks {
Ticks {
locator: Locator::default(),
formatter: Some(Formatter::default()),
txt_modifiers: text::TextModifiers::default(),
txt_props: text::TextProps::default(),
color: theme::Col::Foreground.into(),
}
}
Expand All @@ -741,12 +741,9 @@ pub mod ticks {
pub fn with_formatter(self, formatter: Option<Formatter>) -> Self {
Self { formatter, ..self }
}
/// Returns a new ticks with the specified text modifiers
pub fn with_font(self, txt_modifiers: text::TextModifiers<theme::Color>) -> Self {
Self {
txt_modifiers,
..self
}
/// Returns a new ticks with the specified text properties
pub fn with_font(self, txt_props: text::TextProps<theme::Color>) -> Self {
Self { txt_props, ..self }
}
/// Returns a new ticks with the specified color
pub fn with_color(self, color: theme::Color) -> Self {
Expand All @@ -763,8 +760,8 @@ pub mod ticks {
self.formatter.as_ref()
}
/// Text properties for the ticks labels
pub fn font(&self) -> &text::TextModifiers<theme::Color> {
&self.txt_modifiers
pub fn font(&self) -> &text::TextProps<theme::Color> {
&self.txt_props
}
/// Color for the ticks.
/// Will be used for the labels as well unless a specific color is set in [`font`](Self::font).
Expand Down
8 changes: 4 additions & 4 deletions src/des/colorbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct ColorBar {
pub(crate) pos: Pos,
width: f32,
title: Option<Text>,
ticks_font: text::TextModifiers<theme::Color>,
ticks_font: text::TextProps<theme::Color>,
border: Option<theme::Stroke>,
locator: axis::ticks::Locator,
margin: f32,
Expand All @@ -36,7 +36,7 @@ impl Default for ColorBar {
pos: Pos::default(),
width: defaults::COLORBAR_WIDTH,
title: None,
ticks_font: text::TextModifiers::default(),
ticks_font: text::TextProps::default(),
border: Some(theme::Stroke {
color: theme::Col::Foreground.into(),
width: 1.0,
Expand Down Expand Up @@ -71,7 +71,7 @@ impl ColorBar {
}

/// Set the ticks font properties and return self for chaining
pub fn with_ticks_font(mut self, ticks_font: text::TextModifiers<theme::Color>) -> Self {
pub fn with_ticks_font(mut self, ticks_font: text::TextProps<theme::Color>) -> Self {
self.ticks_font = ticks_font;
self
}
Expand Down Expand Up @@ -110,7 +110,7 @@ impl ColorBar {
}

/// Get the ticks font properties
pub fn ticks_font(&self) -> &text::TextModifiers<theme::Color> {
pub fn ticks_font(&self) -> &text::TextProps<theme::Color> {
&self.ticks_font
}

Expand Down
8 changes: 4 additions & 4 deletions src/des/legend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::text;
#[derive(Debug, Clone, PartialEq)]
pub struct Legend<Pos> {
pos: Pos,
font: text::TextModifiers<theme::Color>,
font: text::TextProps<theme::Color>,
fill: Option<theme::Fill>,
border: Option<theme::Stroke>,
columns: Option<NonZeroU32>,
Expand All @@ -30,7 +30,7 @@ impl<Pos: Default> Default for Legend<Pos> {
fn default() -> Self {
Self {
pos: Pos::default(),
font: text::TextModifiers::default(),
font: text::TextProps::default(),
fill: defaults::legend_fill(),
border: Some(theme::Col::LegendBorder.into()),
columns: None,
Expand Down Expand Up @@ -66,7 +66,7 @@ where

impl<Pos> Legend<Pos> {
/// Get the font configuration for legend entries
pub fn font(&self) -> &text::TextModifiers<theme::Color> {
pub fn font(&self) -> &text::TextProps<theme::Color> {
&self.font
}

Expand Down Expand Up @@ -106,7 +106,7 @@ impl<Pos> Legend<Pos> {
}

/// Set the font configuration for legend entries and return self for chaining
pub fn with_font(self, font: text::TextModifiers<theme::Color>) -> Self {
pub fn with_font(self, font: text::TextProps<theme::Color>) -> Self {
Self { font, ..self }
}

Expand Down
120 changes: 104 additions & 16 deletions src/des/sd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ impl serde::Serialize for Text {
match self {
Text::Plain(text) => serializer.serialize_str(text),
Text::Rich(fmt) => {
let mut seq = serializer.serialize_seq(Some(1))?;
seq.serialize_element(fmt)?;
let mut seq = serializer.serialize_seq(None)?;
for l in fmt.lines() {
seq.serialize_element(l)?;
}
seq.end()
}
Text::RichWithClasses { fmt, classes } => {
Expand All @@ -44,18 +46,48 @@ impl serde::Serialize for Text {
}
}

struct RichPropsMap(Vec<(String, text::TextModifiers<theme::Color>)>);
enum TextPropsMapOrString {
Props(Vec<(String, text::TextProps<theme::Color>)>),
String(String),
}

impl<'de> serde::Deserialize<'de> for RichPropsMap {
impl<'de> serde::Deserialize<'de> for TextPropsMapOrString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let map =
std::collections::HashMap::<String, text::TextModifiers<theme::Color>>::deserialize(
deserializer,
)?;
Ok(RichPropsMap(map.into_iter().collect()))
struct Visitor;

impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = TextPropsMapOrString;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string or a text properties object")
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(TextPropsMapOrString::String(value.to_string()))
}

fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut result = Vec::new();

while let Some((key, value)) =
map.next_entry::<String, text::TextProps<theme::Color>>()?
{
result.push((key, value));
}
Ok(TextPropsMapOrString::Props(result))
}
}

deserializer.deserialize_any(Visitor)
}
}

Expand Down Expand Up @@ -87,16 +119,72 @@ impl<'de> serde::Deserialize<'de> for Text {
let fmt: String = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
let classes: Option<RichPropsMap> = seq.next_element()?;
if let Some(classes) = classes {
Ok(Text::RichWithClasses {
let next = seq.next_element::<TextPropsMapOrString>()?;
match next {
Some(TextPropsMapOrString::Props(props)) => Ok(Text::RichWithClasses {
fmt,
classes: classes.0,
})
} else {
Ok(Text::Rich(fmt))
classes: props,
}),
Some(TextPropsMapOrString::String(s2)) => {
let mut fmt = fmt + "\n" + &s2;
while let Some(s) = seq.next_element::<String>()? {
fmt.push('\n');
fmt.push_str(&s);
}
Ok(Text::Rich(fmt))
}
None => Ok(Text::Rich(fmt)),
}
}

fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut fmt = Option::<String>::None;
let mut classes = Vec::new();
while let Some((key, value)) = map.next_entry::<String, TextPropsMapOrString>()? {
match key.as_str() {
"fmt" => {
if fmt.is_some() {
return Err(serde::de::Error::duplicate_field("fmt"));
}
match value {
TextPropsMapOrString::String(s) => fmt = Some(s),
TextPropsMapOrString::Props(_) => {
return Err(serde::de::Error::custom(
"The 'fmt' field must be a string, not an object",
));
}
}
}
"classes" => {
if !classes.is_empty() {
return Err(serde::de::Error::duplicate_field("classes"));
}
match value {
TextPropsMapOrString::Props(props) => classes = props,
TextPropsMapOrString::String(_) => {
return Err(serde::de::Error::custom(
"The 'classes' field must be an object, not a string",
));
}
}
}
_ => {
return Err(serde::de::Error::unknown_field(
key.as_str(),
&["fmt", "classes"],
));
}
}
}

let Some(fmt) = fmt else {
return Err(serde::de::Error::missing_field("fmt"));
};
Ok(Text::RichWithClasses { fmt, classes })
}
}
deserializer.deserialize_any(TextVisitor)
}
Expand Down
2 changes: 1 addition & 1 deletion src/des/sd/colorbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ impl<'de> serde::Deserialize<'de> for colorbar::ColorBar {
"title" => title: Option<des::Text>,
"border" => border: Option<Option<theme::Stroke>>,
"ticks" => ticks: Option<ticks::Locator>,
"ticksFont" => ticks_font: Option<text::TextModifiers<theme::Color>>,
"ticksFont" => ticks_font: Option<text::TextProps<theme::Color>>,
"margin" => margin: Option<f32>,
);
let mut colorbar = if let Some(pos) = pos {
Expand Down
2 changes: 1 addition & 1 deletion src/des/sd/legend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ where
where
S: serde::Serializer,
{
let font_default = self.font() == &text::TextModifiers::default();
let font_default = self.font() == &text::TextProps::default();
let fill_default = self.fill() == defaults::legend_fill().as_ref();
let border_default = self.border() == Some(&theme::Col::LegendBorder.into());
let columns_default = self.columns().is_none();
Expand Down
Loading
Loading