use cssparser::{BasicParseErrorKind, ParseError, ParseErrorKind};
use selectors::parser::SelectorParseErrorKind;
use std::borrow::Cow;
use crate::{
keyframes::KeyframePreludeParseError,
resources::{font::FontError, image::ImageResourceError},
};
use thiserror::Error;
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum StyleDeclarationBlockParseError {
#[error("failed to parse CSS declaration block `{input}` near `{context}`: {reason}")]
InvalidDeclarationBlock {
input: String,
context: String,
reason: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct StyleSheetParseError {
pub input: Option<String>,
pub context: Option<String>,
pub kind: StyleSheetParseErrorKind,
}
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum StyleSheetParseErrorKind {
#[error("{0}")]
InvalidStyleSheet(String),
#[error("unsupported media feature")]
UnsupportedMediaFeature,
#[error("@property inherits must be true or false")]
PropertyInheritsMustBeBoolean,
#[error("missing `@property` syntax")]
MissingPropertySyntax,
#[error("missing `@property` inherits")]
MissingPropertyInherits,
#[error("@supports cannot mix `and` and `or` without parentheses")]
SupportsMixedAndOrWithoutParentheses,
#[error("@property name must be a custom property")]
PropertyNameMustBeCustomProperty,
#[error("@layer blocks accept at most one name")]
LayerBlockMultipleNames,
#[error("unsupported nested at-rule")]
UnsupportedNestedAtRule,
}
impl std::fmt::Display for StyleSheetParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(context) = &self.context {
write!(
f,
"failed to parse stylesheet near `{context}`: {}",
self.kind
)
} else {
write!(f, "failed to parse stylesheet: {}", self.kind)
}
}
}
impl std::error::Error for StyleSheetParseError {}
impl<'i> From<SelectorParseErrorKind<'i>> for StyleSheetParseError {
fn from(err: SelectorParseErrorKind<'i>) -> Self {
Self::invalid_reason(format!("{err:?}"))
}
}
impl<'i> From<Cow<'i, str>> for StyleSheetParseError {
fn from(err: Cow<'i, str>) -> Self {
Self::invalid_reason(err.into_owned())
}
}
impl<'i> From<KeyframePreludeParseError<'i>> for StyleSheetParseError {
fn from(_err: KeyframePreludeParseError<'i>) -> Self {
Self::invalid_reason(format!("{:?}", BasicParseErrorKind::QualifiedRuleInvalid))
}
}
impl StyleSheetParseError {
pub(crate) fn invalid_reason(reason: impl Into<String>) -> Self {
Self::new(StyleSheetParseErrorKind::InvalidStyleSheet(reason.into()))
}
pub(crate) fn unsupported_media_feature() -> Self {
Self::new(StyleSheetParseErrorKind::UnsupportedMediaFeature)
}
pub(crate) fn property_inherits_must_be_boolean() -> Self {
Self::new(StyleSheetParseErrorKind::PropertyInheritsMustBeBoolean)
}
pub(crate) fn missing_property_syntax() -> Self {
Self::new(StyleSheetParseErrorKind::MissingPropertySyntax)
}
pub(crate) fn missing_property_inherits() -> Self {
Self::new(StyleSheetParseErrorKind::MissingPropertyInherits)
}
pub(crate) fn supports_mixed_and_or_without_parentheses() -> Self {
Self::new(StyleSheetParseErrorKind::SupportsMixedAndOrWithoutParentheses)
}
pub(crate) fn property_name_must_be_custom_property() -> Self {
Self::new(StyleSheetParseErrorKind::PropertyNameMustBeCustomProperty)
}
pub(crate) fn layer_block_multiple_names() -> Self {
Self::new(StyleSheetParseErrorKind::LayerBlockMultipleNames)
}
pub(crate) fn unsupported_nested_at_rule() -> Self {
Self::new(StyleSheetParseErrorKind::UnsupportedNestedAtRule)
}
fn new(kind: StyleSheetParseErrorKind) -> Self {
Self {
input: None,
context: None,
kind,
}
}
fn with_context(self, input: &str, context: &str) -> Self {
Self {
input: Some(input.to_owned()),
context: Some(context.to_owned()),
kind: self.kind,
}
}
pub(crate) fn from_parse_error(
input: &str,
context: &str,
error: ParseError<'_, StyleSheetParseError>,
) -> Self {
match error.kind {
ParseErrorKind::Basic(error) => Self::invalid_reason(format!("{error:?}")),
ParseErrorKind::Custom(error) => error,
}
.with_context(input, context)
}
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum WebPError {
#[error("WebP encoder setup failed")]
EncoderSetupFailed,
#[error("WebP encode failed")]
EncodeFailed,
#[error("WebP encode failed ({error_code})")]
EncodeFailedWithCode {
error_code: String,
},
#[error("{name} must be in 1..={max}, got {value}")]
InvalidDimension {
name: &'static str,
value: u32,
max: u32,
},
#[error("WebP animation frame dimensions must be in 1..={max}, got {width}x{height}")]
InvalidFrameDimensions {
width: u32,
height: u32,
max: u32,
},
#[error("animation must contain at least one frame")]
EmptyAnimation,
#[error(
"frame {index} dimensions {frame_width}x{frame_height} exceed canvas {canvas_width}x{canvas_height}"
)]
FrameExceedsCanvas {
index: usize,
frame_width: u32,
frame_height: u32,
canvas_width: u32,
canvas_height: u32,
},
#[error("all animation frames must have the same dimensions")]
MixedFrameDimensions,
#[error("WebP encoded data is invalid or unsupported")]
InvalidEncodedData,
#[error("WebP container size exceeds supported limits")]
ContainerSizeOverflow,
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error("Image resolution error: {0}")]
ImageResolveError(#[from] ImageResourceError),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("PNG encoding error: {0}")]
PngError(#[from] png::EncodingError),
#[error("WebP encoding error: {0}")]
#[cfg(target_arch = "wasm32")]
WebPEncodingError(#[from] image_webp::EncodingError),
#[error("WebP error: {0}")]
WebPError(#[from] WebPError),
#[error("GIF encoding error: {0}")]
GifEncodingError(#[from] gif::EncodingError),
#[error("Image error: {0}")]
ImageError(#[from] image::ImageError),
#[error("Invalid viewport: width or height cannot be 0")]
InvalidViewport,
#[error("invalid RGBA buffer length: expected {expected} bytes, got {actual}")]
InvalidRgbaBufferLength {
actual: usize,
expected: usize,
},
#[error("{format} animation must contain at least one frame")]
EmptyAnimationFrames {
format: &'static str,
},
#[error("all {format} animation frames must share the same dimensions")]
MixedAnimationFrameDimensions {
format: &'static str,
},
#[error("GIF frame dimensions must be <= {max}x{max}, got {width}x{height}")]
GifFrameDimensionsTooLarge {
width: u32,
height: u32,
max: u16,
},
#[error("Font error: {0}")]
FontError(#[from] FontError),
#[error("Layout error: {0}")]
LayoutError(taffy::TaffyError),
}
impl From<taffy::TaffyError> for Error {
fn from(err: taffy::TaffyError) -> Self {
Self::LayoutError(err)
}
}
pub type Result<T> = std::result::Result<T, Error>;