use std::str::FromStr;
use geo_types::Coord;
use quick_xml::{
Decoder, XmlVersion,
events::{BytesStart, attributes::Attribute},
};
use crate::{Error, Result};
const FILE_COORD_MAX: f64 = ((i32::MAX / 1000) - 1) as f64;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Code {
pub major: u16,
pub minor: u16,
pub patch: u16,
}
impl Code {
pub fn new(major: u16, minor: u16, patch: u16) -> Code {
Code {
major,
minor,
patch,
}
}
}
impl FromStr for Code {
type Err = Error;
fn from_str(value: &str) -> Result<Self> {
let mut parts = value.split('.').take(3);
Ok(Code {
major: parts.next().ok_or(Error::EmptyCode)?.parse()?,
minor: parts.next().and_then(|i| i.parse().ok()).unwrap_or(0),
patch: parts.next().and_then(|i| i.parse().ok()).unwrap_or(0),
})
}
}
impl std::fmt::Display for Code {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.patch > 0 {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
} else if self.minor > 0 {
write!(f, "{}.{}", self.major, self.minor)
} else if self.major > 0 {
write!(f, "{}", self.major)
} else {
write!(f, "")
}
}
}
pub(crate) fn parse_attr_raw<T: FromStr>(value: std::borrow::Cow<'_, [u8]>) -> Result<T> {
let e = match T::from_str(std::str::from_utf8(value.as_ref())?) {
Ok(it) => it,
Err(_err) => return Err(Error::FromStrError),
};
Ok(e)
}
pub(crate) fn try_get_attr_raw<T: FromStr>(
bytes: &BytesStart<'_>,
attr: &str,
) -> Result<Option<T>> {
let a = bytes
.try_get_attribute(attr)?
.map(|a| parse_attr_raw(a.value));
a.transpose()
}
pub(crate) fn parse_attr<T: FromStr>(attr: Attribute<'_>, decoder: Decoder) -> Result<T> {
let e = match T::from_str(&attr.decoded_and_normalized_value(XmlVersion::Explicit1_0, decoder)?)
{
Ok(it) => it,
Err(_err) => return Err(Error::FromStrError),
};
Ok(e)
}
pub(crate) fn try_get_attr<T: FromStr>(bytes: &BytesStart<'_>, attr: &str) -> Result<Option<T>> {
let a = bytes
.try_get_attribute(attr)?
.map(|a| parse_attr(a, bytes.decoder()));
a.transpose()
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct UnitF64(f64);
impl UnitF64 {
pub fn get(self) -> f64 {
self.0
}
pub fn clamped_from(value: f64) -> Self {
if value.is_nan() {
Self(0.)
} else {
Self(value.clamp(0., 1.))
}
}
pub fn one() -> Self {
UnitF64(1.)
}
pub fn zero() -> Self {
UnitF64(0.)
}
}
impl TryFrom<f64> for UnitF64 {
type Error = Error;
fn try_from(v: f64) -> std::result::Result<Self, Self::Error> {
if v.is_finite() && (0.0..=1.0).contains(&v) {
Ok(Self(v))
} else {
Err(Error::NotInUnitInterval)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct NonNegativeF64(f64);
impl NonNegativeF64 {
pub fn get(self) -> f64 {
self.0
}
pub fn clamped_from(value: f64) -> Self {
if value.is_nan() {
Self(0.)
} else {
Self(value.max(0.))
}
}
pub fn one() -> Self {
NonNegativeF64(1.)
}
pub fn zero() -> Self {
NonNegativeF64(0.)
}
pub(crate) fn to_file_value(self) -> Result<u32> {
Ok(to_file_value(self.0)?.try_into()?)
}
pub(crate) fn from_file_value(value: u32) -> Self {
NonNegativeF64(from_file_value(value as i32))
}
}
impl TryFrom<f64> for NonNegativeF64 {
type Error = Error;
fn try_from(v: f64) -> std::result::Result<Self, Self::Error> {
if v >= 0. {
Ok(Self(v))
} else {
Err(Error::NotNonNegativeF64)
}
}
}
pub(crate) fn to_file_coords(map_coord: Coord) -> Result<Coord<i32>> {
Ok(Coord {
x: to_file_value(map_coord.x)?,
y: -to_file_value(map_coord.y)?,
})
}
pub(crate) fn to_file_value(value: f64) -> Result<i32> {
if value.abs() > FILE_COORD_MAX {
return Err(Error::MapCoordOutOfBounds);
}
Ok((value * 1000.).round() as i32)
}
pub(crate) fn from_file_value(value: i32) -> f64 {
value as f64 / 1000.
}
pub(crate) fn from_file_coords(file_coord: Coord<i32>) -> Coord {
Coord {
x: from_file_value(file_coord.x),
y: -from_file_value(file_coord.y),
}
}