use crate::description::parse_helper::Parse;
use crate::description::util::IterUtil;
use crate::validation::{ValidationError, ValidationErrorType, ValidationObject, ValidationResult};
use crate::values::{non_empty_string, ColorCie, Name};
use serde::de::{Error, Unexpected, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
use std::num::NonZeroU8;
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct PhysicalDescriptions {
#[serde(
rename = "Emitters",
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_emitters",
deserialize_with = "deserialize_emitters",
default
)]
pub emitters: Vec<Emitter>,
#[serde(
rename = "Filters",
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_filters",
deserialize_with = "deserialize_filters",
default
)]
pub filters: Vec<Filter>,
#[serde(rename = "ColorSpace")]
pub color_space: Option<ColorSpace>,
#[serde(
rename = "AdditionalColorSpaces",
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_color_spaces",
deserialize_with = "deserialize_color_spaces",
default
)]
pub additional_color_spaces: Vec<ColorSpace>,
#[serde(
rename = "Gamuts",
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_gamuts",
deserialize_with = "deserialize_gamuts",
default
)]
pub gamuts: Vec<Gamut>,
#[serde(
rename = "DMXProfiles",
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_dmx_profiles",
deserialize_with = "deserialize_dmx_profiles",
default
)]
pub dmx_profiles: Vec<DmxProfile>,
#[serde(
rename = "CRIs",
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_cri_groups",
deserialize_with = "deserialize_cri_groups",
default
)]
pub cri_groups: Vec<CriGroup>,
#[serde(
rename = "Connectors",
skip_serializing_if = "Vec::is_empty",
serialize_with = "serialize_connectors",
deserialize_with = "deserialize_connectors",
default
)]
pub connectors: Vec<Connector>,
#[serde(rename = "Properties", default)]
pub properties: Properties,
}
impl PhysicalDescriptions {
pub fn emitter(&self, name: &str) -> Option<&Emitter> {
self.emitters
.iter()
.find(|emitter| emitter.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn filter(&self, name: &str) -> Option<&Filter> {
self.filters
.iter()
.find(|filter| filter.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn color_space(&self, name: &str) -> Option<&ColorSpace> {
if let Some(color_space) = &self.color_space {
if color_space.name.as_ref().map(Name::as_ref) == Some(name) {
return Some(color_space);
}
}
self.additional_color_spaces
.iter()
.find(|space| space.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn gamut(&self, name: &str) -> Option<&Gamut> {
self.gamuts
.iter()
.find(|gamut| gamut.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn dmx_profile(&self, name: &str) -> Option<&DmxProfile> {
self.dmx_profiles
.iter()
.find(|profile| profile.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn connector(&self, name: &str) -> Option<&Connector> {
self.connectors
.iter()
.find(|connector| connector.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn validate(&self, result: &mut ValidationResult) {
let duplicate_emitter_name = self
.emitters
.iter()
.filter_map(|emitter| emitter.name.as_ref())
.find_duplicate();
if let Some(name) = duplicate_emitter_name {
result.errors.push(ValidationError::new(
ValidationObject::Emitter,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
let duplicate_filter_name = self
.filters
.iter()
.filter_map(|filter| filter.name.as_ref())
.find_duplicate();
if let Some(name) = duplicate_filter_name {
result.errors.push(ValidationError::new(
ValidationObject::Filter,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
let duplicate_color_space_name = self
.color_space
.iter()
.chain(self.additional_color_spaces.iter())
.filter_map(|color_space| color_space.name.as_ref())
.find_duplicate();
if let Some(name) = duplicate_color_space_name {
result.errors.push(ValidationError::new(
ValidationObject::ColorSpace,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
let duplicate_gamut_name = self
.gamuts
.iter()
.filter_map(|gamut| gamut.name.as_ref())
.find_duplicate();
if let Some(name) = duplicate_gamut_name {
result.errors.push(ValidationError::new(
ValidationObject::Gamut,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
let duplicate_dmx_profile_name = self
.dmx_profiles
.iter()
.filter_map(|dmx_profile| dmx_profile.name.as_ref())
.find_duplicate();
if let Some(name) = duplicate_dmx_profile_name {
result.errors.push(ValidationError::new(
ValidationObject::DmxProfile,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
let duplicate_connector_name = self
.connectors
.iter()
.filter_map(|connector| connector.name.as_ref())
.find_duplicate();
if let Some(name) = duplicate_connector_name {
result.errors.push(ValidationError::new(
ValidationObject::Connector,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
for emitter in &self.emitters {
emitter.validate(result);
}
for color_space in self
.color_space
.iter()
.chain(self.additional_color_spaces.iter())
{
color_space.validate(result);
}
for gamut in &self.gamuts {
gamut.validate(result);
}
for dmx_profile in &self.dmx_profiles {
dmx_profile.validate(result);
}
for cri_group in &self.cri_groups {
cri_group.validate(result);
}
for connector in &self.connectors {
connector.validate(result);
}
}
}
define_collect_helper!("Emitter" (serialize_emitters, deserialize_emitters) -> Emitter);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Emitter {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(flatten)]
pub optic: EmitterOptic,
#[serde(
rename = "@DiodePart",
skip_serializing_if = "Option::is_none",
deserialize_with = "non_empty_string",
default
)]
pub diode_part: Option<String>,
#[serde(rename = "Measurement", skip_serializing_if = "Vec::is_empty", default)]
pub measurements: Vec<Measurement>,
}
impl Emitter {
pub fn validate(&self, result: &mut ValidationResult) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::Emitter,
None,
ValidationErrorType::MissingName,
));
}
let name = self.name.as_ref();
if self
.measurements
.iter()
.any(|measurement| measurement.luminous_intensity.is_none())
{
result.errors.push(ValidationError::new(
ValidationObject::Emitter,
name.map(Name::to_string),
ValidationErrorType::MissingLuminousIntensity,
));
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum EmitterOptic {
Color {
#[serde(rename = "@Color")]
color: ColorCie,
#[serde(
rename = "@DominantWaveLength",
skip_serializing_if = "Option::is_none",
deserialize_with = "Parse::deserialize"
)]
dominant_wave_length: Option<f64>,
},
WaveLength {
#[serde(rename = "@DominantWaveLength")]
dominant_wave_length: f64,
},
}
define_collect_helper!("Filter" (serialize_filters, deserialize_filters) -> Filter);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Filter {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(rename = "@Color")]
pub color: ColorCie,
#[serde(rename = "Measurement", skip_serializing_if = "Vec::is_empty", default)]
pub measurements: Vec<Measurement>,
}
impl Filter {
pub fn validate(&self, result: &mut ValidationResult) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::Filter,
None,
ValidationErrorType::MissingName,
));
}
let name = self.name.as_ref();
if self
.measurements
.iter()
.any(|measurement| measurement.transmission.is_none())
{
result.errors.push(ValidationError::new(
ValidationObject::Filter,
name.map(Name::to_string),
ValidationErrorType::MissingTransmission,
));
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Measurement {
#[serde(rename = "@Physical")]
pub physical: f64,
#[serde(rename = "@LuminousIntensity", skip_serializing_if = "Option::is_none")]
pub luminous_intensity: Option<f64>,
#[serde(rename = "@Transmission", skip_serializing_if = "Option::is_none")]
pub transmission: Option<f64>,
#[serde(rename = "@InterpolationTo", default)]
pub interpolation_to: Interpolation,
#[serde(
rename = "MeasurementPoint",
skip_serializing_if = "Vec::is_empty",
default
)]
pub points: Vec<MeasurementPoint>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct MeasurementPoint {
#[serde(rename = "@WaveLength")]
pub wave_length: f64,
#[serde(rename = "@Energy")]
pub energy: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum Interpolation {
Step,
Log,
#[default]
#[serde(other)]
Linear,
}
define_collect_helper!("ColorSpace" (serialize_color_spaces, deserialize_color_spaces) -> ColorSpace);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ColorSpace {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(flatten, default)]
pub mode: ColorSpaceMode,
}
impl ColorSpace {
pub fn validate(&self, result: &mut ValidationResult) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::ColorSpace,
None,
ValidationErrorType::MissingName,
));
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
#[serde(tag = "@Mode")]
pub enum ColorSpaceMode {
Custom {
#[serde(rename = "@Red")]
red: ColorCie,
#[serde(rename = "@Green")]
green: ColorCie,
#[serde(rename = "@Blue")]
blue: ColorCie,
#[serde(rename = "@WhitePoint")]
white_point: ColorCie,
},
ProPhoto,
#[serde(rename = "ANSI")]
Ansi,
#[default]
#[serde(other, rename = "sRGB")]
Srgb,
}
define_collect_helper!("Gamut" (serialize_gamuts, deserialize_gamuts) -> Gamut);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Gamut {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(rename = "@Points", skip_serializing_if = "Vec::is_empty")]
pub points: Vec<ColorCie>, }
impl Gamut {
pub fn validate(&self, result: &mut ValidationResult) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::Gamut,
None,
ValidationErrorType::MissingName,
));
}
let name = self.name.as_ref();
if self.points.len() < 3 {
result.errors.push(ValidationError::new(
ValidationObject::Gamut,
name.map(Name::to_string),
ValidationErrorType::EmptyPolygon,
));
}
}
}
define_collect_helper!("DMXProfile" (serialize_dmx_profiles, deserialize_dmx_profiles) -> DmxProfile);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DmxProfile {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(rename = "Point", skip_serializing_if = "Vec::is_empty", default)]
pub points: Vec<Point>,
}
impl DmxProfile {
pub fn validate(&self, result: &mut ValidationResult) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::DmxProfile,
None,
ValidationErrorType::MissingName,
));
}
let name = self.name.as_ref();
if self.points.is_empty() {
result.errors.push(ValidationError::new(
ValidationObject::DmxProfile,
name.map(Name::to_string),
ValidationErrorType::EmptyProfile,
));
}
let duplicate_point = self
.points
.iter()
.map(|point| point.dmx_percentage)
.find_duplicate();
if let Some(point) = duplicate_point {
result.errors.push(ValidationError::new(
ValidationObject::DmxProfile,
name.map(Name::to_string),
ValidationErrorType::DuplicatePoint(point),
));
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Point {
#[serde(rename = "@DMXPercentage", default)]
pub dmx_percentage: f64,
#[serde(rename = "@CFC0", default)]
pub cfc0: f64,
#[serde(rename = "@CFC1", default)]
pub cfc1: f64,
#[serde(rename = "@CFC2", default)]
pub cfc2: f64,
#[serde(rename = "@CFC3", default)]
pub cfc3: f64,
}
define_collect_helper!("CRIGroup" (serialize_cri_groups, deserialize_cri_groups) -> CriGroup);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CriGroup {
#[serde(rename = "@ColorTemperature", default = "default_color_temperature")]
pub color_temperature: f64,
#[serde(rename = "CRI", default)]
pub cris: Vec<Cri>,
}
impl CriGroup {
pub fn validate(&self, result: &mut ValidationResult) {
let duplicate_ces = self.cris.iter().map(|cri| cri.ces).find_duplicate();
if let Some(ces) = duplicate_ces {
result.errors.push(ValidationError::new(
ValidationObject::CriGroup,
None,
ValidationErrorType::DuplicateCriSample(ces),
));
}
}
}
fn default_color_temperature() -> f64 {
6000.
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Cri {
#[serde(rename = "@CES")]
pub ces: Ces,
#[serde(
rename = "@ColorRenderingIndex",
default = "default_color_rendering_index"
)]
pub color_rendering_index: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Ces(NonZeroU8);
impl Ces {
pub fn new(value: u8) -> Option<Ces> {
if value >= 100 {
return None;
}
NonZeroU8::new(value).map(Ces)
}
pub fn get(self) -> u8 {
self.0.get()
}
}
impl Serialize for Ces {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("CES{:0>2}", self.get()))
}
}
impl<'de> Deserialize<'de> for Ces {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct CesVisitor;
impl<'de> Visitor<'de> for CesVisitor {
type Value = Ces;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a value in the range of `CES01`..`CES99`")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
v.strip_prefix("CES")
.and_then(|num_str| num_str.parse::<u8>().ok())
.and_then(Ces::new)
.ok_or_else(|| E::invalid_value(Unexpected::Str(v), &self))
}
}
deserializer.deserialize_str(CesVisitor)
}
}
impl Display for Ces {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.serialize(f)
}
}
fn default_color_rendering_index() -> u8 {
100
}
define_collect_helper!("Connector" (serialize_connectors, deserialize_connectors) -> Connector);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Connector {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(rename = "@Type")]
pub type_: Name,
#[serde(rename = "@DMXBreak", skip_serializing_if = "Option::is_none")]
pub dmx_break: Option<u32>,
#[serde(rename = "@Gender", default)]
pub gender: i32,
#[serde(rename = "@Length", default)]
pub length: f64,
}
impl Connector {
pub fn validate(&self, result: &mut ValidationResult) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::Connector,
None,
ValidationErrorType::MissingName,
));
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
pub struct Properties {
#[serde(
rename = "OperatingTemperature",
skip_serializing_if = "Option::is_none"
)]
pub operating_temperature: Option<OperatingTemperature>,
#[serde(
rename = "Weight",
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_value_float",
deserialize_with = "deserialize_value_float",
default
)]
pub weight: Option<f64>,
#[serde(
rename = "LegHeight",
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_value_float",
deserialize_with = "deserialize_value_float",
default
)]
pub leg_height: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct OperatingTemperature {
#[serde(rename = "@Low", default = "default_low_operating_temperature")]
pub low: f64,
#[serde(rename = "@High", default = "default_high_operating_temperature")]
pub high: f64,
}
fn default_low_operating_temperature() -> f64 {
0.
}
fn default_high_operating_temperature() -> f64 {
40.
}
fn serialize_value_float<S>(value: &Option<f64>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct SeShim<'s> {
#[serde(rename = "@Value")]
value: &'s Option<f64>,
}
SeShim { value }.serialize(serializer)
}
fn deserialize_value_float<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct DeShim {
#[serde(rename = "@Value", default)]
value: Option<f64>,
}
DeShim::deserialize(deserializer).map(|shim| shim.value)
}