use crate::transparency::SoftMask;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BlendMode {
#[default]
Normal,
Compatible,
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
}
impl BlendMode {
#[must_use]
pub fn from_name(name: &[u8]) -> Self {
match name {
b"Multiply" => Self::Multiply,
b"Screen" => Self::Screen,
b"Overlay" => Self::Overlay,
b"Darken" => Self::Darken,
b"Lighten" => Self::Lighten,
b"ColorDodge" => Self::ColorDodge,
b"ColorBurn" => Self::ColorBurn,
b"HardLight" => Self::HardLight,
b"SoftLight" => Self::SoftLight,
b"Difference" => Self::Difference,
b"Exclusion" => Self::Exclusion,
b"Hue" => Self::Hue,
b"Saturation" => Self::Saturation,
b"Color" => Self::Color,
b"Luminosity" => Self::Luminosity,
b"Compatible" => Self::Compatible,
_ => Self::Normal,
}
}
#[must_use]
pub fn needs_backdrop(self) -> bool {
!matches!(self, Self::Normal | Self::Compatible | Self::Multiply)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum RenderIntent {
#[default]
Unknown,
Absolute,
Saturation,
Perceptual,
}
impl RenderIntent {
#[must_use]
pub fn from_name(name: &[u8]) -> Self {
match name.get(..4) {
Some(b"Abso") => Self::Absolute,
Some(b"Satu") => Self::Saturation,
Some(b"Perc") => Self::Perceptual,
_ => Self::Unknown,
}
}
}
#[expect(
clippy::struct_excessive_bools,
reason = "each flag is an independent /ExtGState key, not a mode"
)]
#[derive(Debug, Clone, PartialEq)]
pub struct GeneralState {
pub fill_alpha: f32,
pub stroke_alpha: f32,
pub blend: BlendMode,
pub soft_mask: Option<Arc<SoftMask>>,
pub transfer: Option<Arc<crate::transfer::TransferFunc>>,
pub render_intent: RenderIntent,
pub stroke_overprint: bool,
pub fill_overprint: bool,
pub overprint_mode: i64,
pub flatness: f32,
pub smoothness: f32,
pub stroke_adjust: bool,
pub alpha_is_shape: bool,
pub text_knockout: bool,
}
impl Default for GeneralState {
fn default() -> Self {
Self {
fill_alpha: 1.0,
stroke_alpha: 1.0,
blend: BlendMode::Normal,
soft_mask: None,
transfer: None,
render_intent: RenderIntent::Unknown,
stroke_overprint: false,
fill_overprint: false,
overprint_mode: 0,
flatness: 0.0,
smoothness: 0.0,
stroke_adjust: false,
alpha_is_shape: false,
text_knockout: false,
}
}
}
impl GeneralState {
pub fn enter_transparency_group(&mut self) {
self.blend = BlendMode::Normal;
self.stroke_alpha = 1.0;
self.fill_alpha = 1.0;
self.soft_mask = None;
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unreadable_literal,
clippy::float_cmp,
clippy::indexing_slicing,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
reason = "test fixtures quote oracle vectors verbatim and compare exactly"
)]
use super::{BlendMode, GeneralState, RenderIntent};
#[test]
fn an_unknown_blend_name_is_normal() {
assert_eq!(BlendMode::from_name(b"Multiply"), BlendMode::Multiply);
assert_eq!(BlendMode::from_name(b"Luminosity"), BlendMode::Luminosity);
assert_eq!(BlendMode::from_name(b"Normal"), BlendMode::Normal);
assert_eq!(BlendMode::from_name(b"NotAMode"), BlendMode::Normal);
assert_eq!(BlendMode::from_name(b""), BlendMode::Normal);
assert_eq!(BlendMode::from_name(b"multiply"), BlendMode::Normal);
}
#[test]
fn backdrop_is_needed_past_multiply() {
assert!(!BlendMode::Normal.needs_backdrop());
assert!(!BlendMode::Compatible.needs_backdrop());
assert!(!BlendMode::Multiply.needs_backdrop());
assert!(BlendMode::Screen.needs_backdrop());
assert!(BlendMode::Luminosity.needs_backdrop());
}
#[test]
fn render_intents_match_on_four_bytes() {
assert_eq!(
RenderIntent::from_name(b"AbsoluteColorimetric"),
RenderIntent::Absolute
);
assert_eq!(RenderIntent::from_name(b"Abso"), RenderIntent::Absolute);
assert_eq!(
RenderIntent::from_name(b"Perceptual"),
RenderIntent::Perceptual
);
assert_eq!(RenderIntent::from_name(b"Rel"), RenderIntent::Unknown);
assert_eq!(RenderIntent::from_name(b""), RenderIntent::Unknown);
}
#[test]
fn entering_a_group_clears_exactly_four_parameters() {
let mut s = GeneralState {
fill_alpha: 0.5,
stroke_alpha: 0.25,
blend: BlendMode::Multiply,
overprint_mode: 7,
flatness: 3.0,
..GeneralState::default()
};
s.enter_transparency_group();
assert!((s.fill_alpha - 1.0).abs() < 1e-6);
assert!((s.stroke_alpha - 1.0).abs() < 1e-6);
assert_eq!(s.blend, BlendMode::Normal);
assert!(s.soft_mask.is_none());
assert_eq!(s.overprint_mode, 7);
assert!((s.flatness - 3.0).abs() < 1e-6);
}
#[test]
fn the_defaults_are_fully_opaque_and_unblended() {
let s = GeneralState::default();
assert!((s.fill_alpha - 1.0).abs() < 1e-6);
assert!((s.stroke_alpha - 1.0).abs() < 1e-6);
assert_eq!(s.blend, BlendMode::Normal);
assert!(s.soft_mask.is_none());
assert!(s.transfer.is_none());
}
}