use crate::{
Bounds, DevicePixels, FontId, ForegroundDependency, Pixels, Point, PreparedRasterStyle,
RasterColorExt, Rgba8, Size,
};
use derive_more::Deref;
use std::hash::{Hash, Hasher};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub struct GlyphId(pub u32);
#[derive(Clone, Debug, PartialEq)]
#[expect(missing_docs)]
pub struct RenderGlyphParams {
pub font_id: FontId,
pub glyph_id: GlyphId,
pub font_size: Pixels,
pub subpixel_variant: Point<u8>,
pub scale_factor: f32,
pub raster_style: PreparedRasterStyle,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum GlyphRenderMode {
Grayscale,
Subpixel,
Color,
}
impl GlyphRenderMode {
pub const fn rasterized_format(self) -> RasterizedGlyphFormat {
match self {
Self::Grayscale => RasterizedGlyphFormat::AlphaMask,
Self::Subpixel => RasterizedGlyphFormat::BgraSubpixelMask,
Self::Color => RasterizedGlyphFormat::BgraColor,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct RasterStyleRequest {
pub font_id: FontId,
pub glyph_id: GlyphId,
pub scene_color: crate::Rgba,
pub requested_mode: GlyphRenderMode,
pub foreground_dependency: ForegroundDependency,
}
impl RasterStyleRequest {
pub fn normalized_color(self) -> Rgba8 {
let color = self.scene_color.quantize_raster_color();
if self.foreground_dependency == ForegroundDependency::AlphaOnly {
return Rgba8::new(0, 0, 0, color.alpha);
}
color
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RasterizedGlyphFormat {
AlphaMask,
BgraSubpixelMask,
BgraColor,
}
#[derive(Clone, Debug)]
pub struct RasterizedGlyph {
pub bounds: Bounds<DevicePixels>,
pub size: Size<DevicePixels>,
pub format: RasterizedGlyphFormat,
pub pixels: Vec<u8>,
}
impl RasterizedGlyph {
pub fn empty(format: RasterizedGlyphFormat) -> Self {
Self {
bounds: Bounds::default(),
size: Size::default(),
format,
pixels: Vec::new(),
}
}
pub fn validate(&self) -> crate::Result<()> {
anyhow::ensure!(
self.bounds.size == self.size,
"glyph raster bounds size {:?} does not match buffer size {:?}",
self.bounds.size,
self.size
);
let width: usize = self
.size
.width
.0
.try_into()
.map_err(|_| anyhow::anyhow!("glyph raster width is negative"))?;
let height: usize = self
.size
.height
.0
.try_into()
.map_err(|_| anyhow::anyhow!("glyph raster height is negative"))?;
if width == 0 || height == 0 {
anyhow::ensure!(
width == 0 && height == 0 && self.pixels.is_empty(),
"empty glyph raster must have zero size and no pixels"
);
return Ok(());
}
let bytes_per_pixel = match self.format {
RasterizedGlyphFormat::AlphaMask => 1,
RasterizedGlyphFormat::BgraSubpixelMask | RasterizedGlyphFormat::BgraColor => 4,
};
let expected_len = width
.checked_mul(height)
.and_then(|pixels| pixels.checked_mul(bytes_per_pixel))
.ok_or_else(|| anyhow::anyhow!("glyph raster byte count overflow"))?;
anyhow::ensure!(
self.pixels.len() == expected_len,
"glyph raster format {:?} requires {expected_len} bytes for {}x{}, got {}",
self.format,
width,
height,
self.pixels.len()
);
Ok(())
}
}
#[derive(Clone, Debug, Deref)]
pub struct ValidatedRasterizedGlyph(RasterizedGlyph);
impl ValidatedRasterizedGlyph {
pub fn new(glyph: RasterizedGlyph) -> crate::Result<Self> {
glyph.validate()?;
Ok(Self(glyph))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct GlyphAtlasEntry {
pub tile: Option<crate::AtlasTile>,
pub bounds: Bounds<DevicePixels>,
pub format: RasterizedGlyphFormat,
}
impl Eq for RenderGlyphParams {}
impl Hash for RenderGlyphParams {
fn hash<H: Hasher>(&self, state: &mut H) {
self.font_id.0.hash(state);
self.glyph_id.0.hash(state);
self.font_size.0.to_bits().hash(state);
self.subpixel_variant.hash(state);
self.scale_factor.to_bits().hash(state);
self.raster_style.hash(state);
}
}