use std::sync::Arc;
use smallvec::SmallVec;
use crate::content::graphics_state::Matrix;
use super::intent::{PaintKind, PaintSide};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct InkName(pub(crate) String);
impl InkName {
pub(crate) fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
pub(crate) fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone)]
pub(crate) enum ResolvedColor {
Rgba { r: f32, g: f32, b: f32, a: f32 },
Cmyk {
c: f32,
m: f32,
y: f32,
k: f32,
a: f32,
},
IccCmyk {
r: f32,
g: f32,
b: f32,
c: f32,
m: f32,
y: f32,
k: f32,
a: f32,
},
PerChannel {
channels: Box<SmallVec<[(InkName, f32); 8]>>,
a: f32,
},
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) enum InkSelector {
#[default]
Listed,
All,
None,
}
#[derive(Debug, Clone)]
pub(crate) struct OverprintPlan {
pub(crate) enabled: bool,
pub(crate) mode: u8,
pub(crate) participating: SmallVec<[ParticipatingChannel; 8]>,
pub(crate) selector: InkSelector,
pub(crate) all_tint: f32,
pub(crate) spot_source: Option<SpotSource>,
pub(crate) alt_cmyk_fallback: Option<[f32; 4]>,
}
#[derive(Debug, Clone)]
pub(crate) struct SpotSource {
pub(crate) ink: InkName,
pub(crate) tint: f32,
}
#[derive(Debug, Clone)]
pub(crate) struct ParticipatingChannel {
pub(crate) ink: InkName,
pub(crate) value: f32,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum BlendPlan {
Native(tiny_skia::BlendMode),
Simulated(&'static str),
}
#[derive(Debug, Clone)]
pub(crate) enum ClipPlan {
None,
Mask(Arc<tiny_skia::Mask>),
}
pub(crate) struct ResolvedPaintCmd<'a> {
pub(crate) kind: PaintKind<'a>,
pub(crate) side: PaintSide,
pub(crate) color: ResolvedColor,
pub(crate) overprint: OverprintPlan,
pub(crate) blend: BlendPlan,
pub(crate) clip: ClipPlan,
pub(crate) ctm: Matrix,
}
#[cfg(test)]
mod tests {
use super::*;
use smallvec::smallvec;
#[test]
fn ink_name_round_trip() {
let i = InkName::new("PANTONE 185 C");
assert_eq!(i.as_str(), "PANTONE 185 C");
}
#[test]
fn ink_name_equality_is_string_equality() {
assert_eq!(InkName::new("Cyan"), InkName::new("Cyan"));
assert_ne!(InkName::new("Cyan"), InkName::new("cyan"));
}
#[test]
fn overprint_plan_disabled_is_no_op_marker() {
let plan = OverprintPlan {
enabled: false,
mode: 0,
participating: SmallVec::new(),
selector: InkSelector::Listed,
all_tint: 0.0,
spot_source: None,
alt_cmyk_fallback: None,
};
assert!(!plan.enabled);
}
#[test]
fn overprint_plan_participating_inline_capacity() {
let plan = OverprintPlan {
enabled: true,
mode: 0,
participating: smallvec![
ParticipatingChannel {
ink: InkName::new("Cyan"),
value: 0.5
},
ParticipatingChannel {
ink: InkName::new("Magenta"),
value: 0.0
},
ParticipatingChannel {
ink: InkName::new("Yellow"),
value: 0.3
},
ParticipatingChannel {
ink: InkName::new("Black"),
value: 0.1
},
],
selector: InkSelector::Listed,
all_tint: 0.0,
spot_source: None,
alt_cmyk_fallback: None,
};
assert_eq!(plan.participating.len(), 4);
assert!(!plan.participating.spilled());
}
#[test]
fn resolved_color_rgba_includes_alpha() {
let c = ResolvedColor::Rgba {
r: 1.0,
g: 0.5,
b: 0.25,
a: 0.75,
};
match c {
ResolvedColor::Rgba { a, .. } => assert!((a - 0.75).abs() < 1e-6),
_ => panic!("expected Rgba"),
}
}
#[test]
fn blend_plan_native_carries_skia_mode() {
let p = BlendPlan::Native(tiny_skia::BlendMode::Multiply);
match p {
BlendPlan::Native(m) => assert_eq!(m, tiny_skia::BlendMode::Multiply),
BlendPlan::Simulated(_) => panic!("expected Native"),
}
}
#[test]
fn clip_plan_mask_is_arc_shared() {
let mask = Arc::new(tiny_skia::Mask::new(4, 4).expect("4x4 mask allocates"));
let plan_a = ClipPlan::Mask(mask.clone());
let plan_b = ClipPlan::Mask(mask.clone());
match (&plan_a, &plan_b) {
(ClipPlan::Mask(a), ClipPlan::Mask(b)) => assert!(Arc::ptr_eq(a, b)),
_ => panic!("both should be Mask"),
}
}
}