use std::io::Write;
use super::*;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ResolvedStyle {
requested: AvatarStyleOptions,
effective: AvatarStyleOptions,
automatically_derived: bool,
}
impl ResolvedStyle {
pub const fn requested(self) -> AvatarStyleOptions {
self.requested
}
pub const fn effective(self) -> AvatarStyleOptions {
self.effective
}
pub const fn is_automatically_derived(self) -> bool {
self.automatically_derived
}
pub fn applied_legacy_fallbacks(self) -> bool {
self.requested.accessory != self.effective.accessory
|| self.requested.expression != self.effective.expression
}
pub fn ignored_accessory(self) -> bool {
self.requested.accessory != self.effective.accessory
}
pub fn ignored_expression(self) -> bool {
self.requested.expression != self.effective.expression
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LayoutReport {
spec: AvatarSpec,
style: ResolvedStyle,
capabilities: AvatarFamilyCapabilities,
}
impl LayoutReport {
pub const fn spec(self) -> AvatarSpec {
self.spec
}
pub const fn resolved_style(self) -> ResolvedStyle {
self.style
}
pub const fn family_capabilities(self) -> AvatarFamilyCapabilities {
self.capabilities
}
pub const fn catalog_version(self) -> CatalogVersion {
CatalogVersion::CURRENT
}
pub const fn render_contract_id(self) -> RenderContractId {
RenderContractId::CURRENT
}
}
pub struct RasterSurfaceMut<'a> {
pixels: &'a mut [u8],
width: u32,
height: u32,
stride: usize,
required_len: usize,
}
impl std::fmt::Debug for RasterSurfaceMut<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RasterSurfaceMut")
.field("pixels", &format_args!("[{} bytes]", self.pixels.len()))
.field("width", &self.width)
.field("height", &self.height)
.field("stride", &self.stride)
.field("required_len", &self.required_len)
.finish()
}
}
impl<'a> RasterSurfaceMut<'a> {
pub fn new_rgba8(
pixels: &'a mut [u8],
width: u32,
height: u32,
stride: usize,
) -> Result<Self, RasterSurfaceError> {
if width == 0 || height == 0 {
return Err(RasterSurfaceError::ZeroDimension { width, height });
}
let width_usize = usize::try_from(width).map_err(|_| RasterSurfaceError::LengthOverflow)?;
let height_usize =
usize::try_from(height).map_err(|_| RasterSurfaceError::LengthOverflow)?;
let minimum_stride = width_usize
.checked_mul(AVATAR_RGBA_BYTES_PER_PIXEL)
.ok_or(RasterSurfaceError::LengthOverflow)?;
if stride < minimum_stride {
return Err(RasterSurfaceError::StrideTooSmall {
minimum: minimum_stride,
actual: stride,
});
}
let required = stride
.checked_mul(height_usize)
.ok_or(RasterSurfaceError::LengthOverflow)?;
if pixels.len() < required {
return Err(RasterSurfaceError::BufferTooSmall {
required,
actual: pixels.len(),
});
}
Ok(Self {
pixels,
width,
height,
stride,
required_len: required,
})
}
pub const fn width(&self) -> u32 {
self.width
}
pub const fn height(&self) -> u32 {
self.height
}
pub const fn stride(&self) -> usize {
self.stride
}
pub const fn required_len(&self) -> usize {
self.required_len
}
pub const fn provided_len(&self) -> usize {
self.pixels.len()
}
pub fn pixels(&self) -> &[u8] {
self.pixels
}
pub fn pixels_mut(&mut self) -> &mut [u8] {
self.pixels
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RasterSurfaceError {
ZeroDimension { width: u32, height: u32 },
StrideTooSmall { minimum: usize, actual: usize },
BufferTooSmall { required: usize, actual: usize },
LengthOverflow,
DimensionMismatch {
expected_width: u32,
expected_height: u32,
actual_width: u32,
actual_height: u32,
},
RendererOutputMismatch {
expected_width: u32,
expected_height: u32,
actual_width: u32,
actual_height: u32,
expected_len: usize,
actual_len: usize,
},
Render(AvatarSpecError),
}
impl From<AvatarSpecError> for RasterSurfaceError {
fn from(error: AvatarSpecError) -> Self {
Self::Render(error)
}
}
impl std::fmt::Display for RasterSurfaceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ZeroDimension { width, height } => {
write!(
f,
"raster surface dimensions must be non-zero, got {width}x{height}"
)
}
Self::StrideTooSmall { minimum, actual } => {
write!(
f,
"RGBA8 stride must be at least {minimum} bytes, got {actual}"
)
}
Self::BufferTooSmall { required, actual } => {
write!(f, "raster surface requires {required} bytes, got {actual}")
}
Self::LengthOverflow => f.write_str("raster surface length calculation overflowed"),
Self::DimensionMismatch {
expected_width,
expected_height,
actual_width,
actual_height,
} => write!(
f,
"raster surface dimensions must be {expected_width}x{expected_height}, got {actual_width}x{actual_height}"
),
Self::RendererOutputMismatch {
expected_width,
expected_height,
actual_width,
actual_height,
expected_len,
actual_len,
} => write!(
f,
"renderer output must be {expected_width}x{expected_height} with {expected_len} RGBA8 bytes, got {actual_width}x{actual_height} with {actual_len} bytes"
),
Self::Render(error) => error.fmt(f),
}
}
}
impl std::error::Error for RasterSurfaceError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Render(error) => Some(error),
_ => None,
}
}
}
#[derive(Clone)]
pub struct PreparedAvatar {
plan: AvatarRenderPlan,
resolved_style: ResolvedStyle,
layout_report: LayoutReport,
resource_budget: ResourceBudget,
}
impl std::fmt::Debug for PreparedAvatar {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PreparedAvatar")
.field("identity", &"[REDACTED]")
.field("layout_report", &self.layout_report)
.field("resource_budget", &self.resource_budget)
.finish()
}
}
impl PreparedAvatar {
pub(crate) fn from_request(request: AvatarRequest) -> Result<Self, AvatarRequestError> {
let (identity, spec, explicit_style, compatibility) = request.into_parts();
spec.validate()?;
let automatically_derived = explicit_style.is_none();
let requested =
explicit_style.unwrap_or_else(|| AvatarStyleOptions::from_identity(&identity));
if explicit_style.is_some() && matches!(compatibility, AvatarCompatibilityMode::Strict) {
requested.validate_strict()?;
}
let effective = requested.canonicalized_for_family();
let plan = AvatarRenderPlan::from_identity(spec, identity, effective)?;
Ok(Self::new(plan, requested, effective, automatically_derived))
}
pub(crate) fn from_legacy_plan(
mut plan: AvatarRenderPlan,
automatically_derived: bool,
) -> Self {
let requested = plan.style();
let effective = requested.canonicalized_for_family();
plan.set_style(effective);
Self::new(plan, requested, effective, automatically_derived)
}
fn new(
plan: AvatarRenderPlan,
requested: AvatarStyleOptions,
effective: AvatarStyleOptions,
automatically_derived: bool,
) -> Self {
let spec = plan.spec();
let resolved_style = ResolvedStyle {
requested,
effective,
automatically_derived,
};
let layout_report = LayoutReport {
spec,
style: resolved_style,
capabilities: effective.kind.capabilities(),
};
Self {
plan,
resolved_style,
layout_report,
resource_budget: ResourceBudget::new(spec),
}
}
pub const fn spec(&self) -> AvatarSpec {
self.layout_report.spec
}
pub const fn resolved_style(&self) -> ResolvedStyle {
self.resolved_style
}
pub const fn layout_report(&self) -> LayoutReport {
self.layout_report
}
pub const fn resource_budget(&self) -> ResourceBudget {
self.resource_budget
}
pub fn identity_cache_key(&self) -> IdentityCacheKey {
self.plan.identity_cache_key()
}
pub fn avatar_asset_key(&self) -> AvatarAssetKey {
self.plan.avatar_asset_key()
}
pub fn encoded_asset_key(&self, format: AvatarOutputFormat) -> SemanticEncodedAssetKey {
self.plan.encoded_asset_key(format)
}
pub fn encoded_asset_key_for_build(
&self,
format: AvatarOutputFormat,
build_id: EncoderBuildId,
) -> BuildEncodedAssetKey {
self.plan.encoded_asset_key_for_build(format, build_id)
}
pub fn render(&self) -> Result<RgbaImage, AvatarSpecError> {
self.plan.render_rgba()
}
pub fn render_into(
&self,
surface: &mut RasterSurfaceMut<'_>,
) -> Result<(), RasterSurfaceError> {
self.render_into_with(surface, || self.plan.render_rgba())
}
fn render_into_with<F>(
&self,
surface: &mut RasterSurfaceMut<'_>,
render: F,
) -> Result<(), RasterSurfaceError>
where
F: FnOnce() -> Result<RgbaImage, AvatarSpecError>,
{
let spec = self.spec();
if (surface.width(), surface.height()) != (spec.width(), spec.height()) {
return Err(RasterSurfaceError::DimensionMismatch {
expected_width: spec.width(),
expected_height: spec.height(),
actual_width: surface.width(),
actual_height: surface.height(),
});
}
let image = SanitizingRgbaImage::new(render()?);
copy_rgba_image_into_surface(spec, image.as_image(), surface)
}
pub fn render_svg(&self) -> String {
self.plan.render_svg()
}
pub fn write_svg<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
writer.write_all(self.plan.render_svg().as_bytes())
}
pub fn encode(&self, format: AvatarOutputFormat) -> ImageResult<Vec<u8>> {
let image = self
.plan
.render_rgba()
.map_err(avatar_spec_error_to_image_error)?;
encode_owned_rgba_image(image, format)
}
pub fn encode_to_writer<W: Write>(
&self,
format: AvatarOutputFormat,
writer: &mut W,
) -> ImageResult<()> {
let image = SanitizingRgbaImage::new(
self.plan
.render_rgba()
.map_err(avatar_spec_error_to_image_error)?,
);
encode_into_writer(image.as_image(), format, writer)
}
}
#[cfg(test)]
mod tests;