use smallvec::SmallVec;
use crate::content::graphics_state::GraphicsState;
use super::intent::PaintSide;
use super::resolved::{InkName, InkSelector, OverprintPlan, ParticipatingChannel, ResolvedColor};
pub(crate) struct OverprintResolver;
impl OverprintResolver {
pub(crate) const fn new() -> Self {
Self
}
pub(crate) fn resolve(
&self,
gs: &GraphicsState,
side: PaintSide,
color: &ResolvedColor,
) -> OverprintPlan {
let enabled = match side {
PaintSide::Fill => gs.fill_overprint,
PaintSide::Stroke => gs.stroke_overprint,
};
let mode = gs.overprint_mode;
let participating: SmallVec<[ParticipatingChannel; 8]> = match color {
ResolvedColor::Rgba { .. } => {
SmallVec::new()
},
ResolvedColor::Cmyk { c, m, y, k, .. } | ResolvedColor::IccCmyk { c, m, y, k, .. } => {
let mut v = SmallVec::new();
v.push(ParticipatingChannel {
ink: InkName::new("Cyan"),
value: *c,
});
v.push(ParticipatingChannel {
ink: InkName::new("Magenta"),
value: *m,
});
v.push(ParticipatingChannel {
ink: InkName::new("Yellow"),
value: *y,
});
v.push(ParticipatingChannel {
ink: InkName::new("Black"),
value: *k,
});
v
},
ResolvedColor::PerChannel { channels, .. } => channels
.iter()
.map(|(ink, v)| ParticipatingChannel {
ink: ink.clone(),
value: *v,
})
.collect(),
};
OverprintPlan {
enabled,
mode,
participating,
selector: InkSelector::Listed,
all_tint: 0.0,
spot_source: None,
alt_cmyk_fallback: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fresh_gs() -> GraphicsState {
GraphicsState::new()
}
#[test]
fn default_state_yields_disabled_plan() {
let gs = fresh_gs();
let color = ResolvedColor::Rgba {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
let r = OverprintResolver::new();
let plan = r.resolve(&gs, PaintSide::Fill, &color);
assert!(!plan.enabled);
assert_eq!(plan.mode, 0);
}
#[test]
fn fill_op_reads_lowercase_op() {
let mut gs = fresh_gs();
gs.fill_overprint = true;
gs.stroke_overprint = false;
let color = ResolvedColor::Cmyk {
c: 0.5,
m: 0.0,
y: 0.0,
k: 0.0,
a: 1.0,
};
let plan = OverprintResolver::new().resolve(&gs, PaintSide::Fill, &color);
assert!(plan.enabled);
}
#[test]
fn stroke_op_reads_uppercase_op() {
let mut gs = fresh_gs();
gs.fill_overprint = false;
gs.stroke_overprint = true;
let color = ResolvedColor::Cmyk {
c: 0.5,
m: 0.0,
y: 0.0,
k: 0.0,
a: 1.0,
};
let plan = OverprintResolver::new().resolve(&gs, PaintSide::Stroke, &color);
assert!(plan.enabled);
}
#[test]
fn cmyk_color_lists_four_process_inks_with_values() {
let mut gs = fresh_gs();
gs.fill_overprint = true;
let color = ResolvedColor::Cmyk {
c: 0.1,
m: 0.2,
y: 0.3,
k: 0.4,
a: 1.0,
};
let plan = OverprintResolver::new().resolve(&gs, PaintSide::Fill, &color);
assert_eq!(plan.participating.len(), 4);
assert_eq!(plan.participating[0].ink, InkName::new("Cyan"));
assert!((plan.participating[0].value - 0.1).abs() < 1e-6);
assert_eq!(plan.participating[1].ink, InkName::new("Magenta"));
assert!((plan.participating[1].value - 0.2).abs() < 1e-6);
assert_eq!(plan.participating[3].ink, InkName::new("Black"));
assert!((plan.participating[3].value - 0.4).abs() < 1e-6);
}
#[test]
fn rgb_color_lists_no_participating_channels() {
let mut gs = fresh_gs();
gs.fill_overprint = true;
let color = ResolvedColor::Rgba {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
let plan = OverprintResolver::new().resolve(&gs, PaintSide::Fill, &color);
assert!(plan.participating.is_empty());
}
#[test]
fn opm_passthrough() {
let mut gs = fresh_gs();
gs.overprint_mode = 1;
gs.fill_overprint = true;
let color = ResolvedColor::Cmyk {
c: 0.0,
m: 0.5,
y: 0.0,
k: 0.0,
a: 1.0,
};
let plan = OverprintResolver::new().resolve(&gs, PaintSide::Fill, &color);
assert_eq!(plan.mode, 1);
}
}