use super::{Handle, HandleStore};
use std::ffi::{CStr, c_char};
use std::sync::LazyLock;
pub const FZ_MAX_SEPARATIONS: usize = 64;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SeparationBehavior {
Composite = 0,
Spot = 1,
Disabled = 2,
}
#[derive(Debug, Clone)]
pub struct Separation {
pub name: String,
pub cmyk: [f32; 4],
pub behavior: SeparationBehavior,
pub is_all: bool,
pub is_none: bool,
}
impl Default for Separation {
fn default() -> Self {
Self {
name: String::new(),
cmyk: [0.0, 0.0, 0.0, 0.0],
behavior: SeparationBehavior::Composite,
is_all: false,
is_none: false,
}
}
}
#[derive(Debug, Clone)]
pub struct Separations {
pub seps: Vec<Separation>,
pub controllable: bool,
}
impl Default for Separations {
fn default() -> Self {
Self {
seps: Vec::new(),
controllable: true,
}
}
}
pub static SEPARATIONS: LazyLock<HandleStore<Separations>> = LazyLock::new(HandleStore::new);
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_separations(_ctx: Handle, controllable: i32) -> Handle {
let seps = Separations {
controllable: controllable != 0,
..Default::default()
};
SEPARATIONS.insert(seps)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_add_separation(
_ctx: Handle,
seps: Handle,
name: *const c_char,
_colorspace: u64,
cmyk_c: f32,
cmyk_m: f32,
cmyk_y: f32,
cmyk_k: f32,
) -> i32 {
let sep_name = if name.is_null() {
String::new()
} else {
let c_str = unsafe { CStr::from_ptr(name) };
c_str.to_str().unwrap_or("").to_string()
};
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(mut guard) = seps_arc.lock() {
if guard.seps.len() >= FZ_MAX_SEPARATIONS {
return -1;
}
let index = guard.seps.len() as i32;
let sep = Separation {
name: sep_name,
cmyk: [
cmyk_c.clamp(0.0, 1.0),
cmyk_m.clamp(0.0, 1.0),
cmyk_y.clamp(0.0, 1.0),
cmyk_k.clamp(0.0, 1.0),
],
behavior: SeparationBehavior::Composite,
is_all: false,
is_none: false,
};
guard.seps.push(sep);
return index;
}
}
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_add_separation_all(_ctx: Handle, seps: Handle) -> i32 {
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(mut guard) = seps_arc.lock() {
if guard.seps.len() >= FZ_MAX_SEPARATIONS {
return -1;
}
let index = guard.seps.len() as i32;
let sep = Separation {
name: "All".to_string(),
cmyk: [0.0, 0.0, 0.0, 1.0],
behavior: SeparationBehavior::Composite,
is_all: true,
is_none: false,
};
guard.seps.push(sep);
return index;
}
}
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_add_separation_none(_ctx: Handle, seps: Handle) -> i32 {
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(mut guard) = seps_arc.lock() {
if guard.seps.len() >= FZ_MAX_SEPARATIONS {
return -1;
}
let index = guard.seps.len() as i32;
let sep = Separation {
name: "None".to_string(),
cmyk: [0.0, 0.0, 0.0, 0.0],
behavior: SeparationBehavior::Disabled,
is_all: false,
is_none: true,
};
guard.seps.push(sep);
return index;
}
}
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_count_separations(_ctx: Handle, seps: Handle) -> i32 {
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(guard) = seps_arc.lock() {
return guard.seps.len() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_separation_name(_ctx: Handle, seps: Handle, idx: i32) -> *const c_char {
static EMPTY: &[u8] = b"\0";
if idx < 0 {
return EMPTY.as_ptr().cast();
}
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(guard) = seps_arc.lock() {
if let Some(sep) = guard.seps.get(idx as usize) {
return sep.name.as_ptr().cast();
}
}
}
EMPTY.as_ptr().cast()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_separations_controllable(_ctx: Handle, seps: Handle) -> i32 {
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(guard) = seps_arc.lock() {
return i32::from(guard.controllable);
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_separation_current_behavior(_ctx: Handle, seps: Handle, idx: i32) -> i32 {
if idx < 0 {
return SeparationBehavior::Disabled as i32;
}
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(guard) = seps_arc.lock() {
if let Some(sep) = guard.seps.get(idx as usize) {
return sep.behavior as i32;
}
}
}
SeparationBehavior::Disabled as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_separation_behavior(_ctx: Handle, seps: Handle, idx: i32, behavior: i32) {
if idx < 0 {
return;
}
let b = match behavior {
0 => SeparationBehavior::Composite,
1 => SeparationBehavior::Spot,
_ => SeparationBehavior::Disabled,
};
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(mut guard) = seps_arc.lock() {
if !guard.controllable {
return;
}
if let Some(sep) = guard.seps.get_mut(idx as usize) {
sep.behavior = b;
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_separation_is_all(_ctx: Handle, seps: Handle, idx: i32) -> i32 {
if idx < 0 {
return 0;
}
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(guard) = seps_arc.lock() {
if let Some(sep) = guard.seps.get(idx as usize) {
return i32::from(sep.is_all);
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_separation_is_none(_ctx: Handle, seps: Handle, idx: i32) -> i32 {
if idx < 0 {
return 0;
}
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(guard) = seps_arc.lock() {
if let Some(sep) = guard.seps.get(idx as usize) {
return i32::from(sep.is_none);
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_separation_equivalent(_ctx: Handle, seps: Handle, idx: i32, cmyk: *mut f32) {
if cmyk.is_null() || idx < 0 {
return;
}
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(guard) = seps_arc.lock() {
if let Some(sep) = guard.seps.get(idx as usize) {
let cmyk_slice = unsafe { std::slice::from_raw_parts_mut(cmyk, 4) };
cmyk_slice.copy_from_slice(&sep.cmyk);
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_separation_equivalent(
_ctx: Handle,
seps: Handle,
idx: i32,
cmyk: *const f32,
) {
if cmyk.is_null() || idx < 0 {
return;
}
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(mut guard) = seps_arc.lock() {
if let Some(sep) = guard.seps.get_mut(idx as usize) {
let cmyk_slice = unsafe { std::slice::from_raw_parts(cmyk, 4) };
sep.cmyk.copy_from_slice(cmyk_slice);
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_convert_separation_colors(
_ctx: Handle,
seps: Handle,
src: *const f32,
src_n: i32,
dst: *mut f32,
dst_n: i32,
) {
if src.is_null() || dst.is_null() || src_n <= 0 || dst_n <= 0 {
return;
}
let src_slice = unsafe { std::slice::from_raw_parts(src, src_n as usize) };
let dst_slice = unsafe { std::slice::from_raw_parts_mut(dst, dst_n as usize) };
dst_slice.fill(0.0);
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(guard) = seps_arc.lock() {
for (i, sep) in guard.seps.iter().enumerate() {
if i >= src_n as usize {
break;
}
if sep.behavior == SeparationBehavior::Disabled {
continue;
}
let intensity = src_slice[i];
if intensity <= 0.0 {
continue;
}
if dst_n >= 4 {
for j in 0..4 {
dst_slice[j] = (dst_slice[j] + sep.cmyk[j] * intensity).min(1.0);
}
} else if dst_n >= 3 {
let c = sep.cmyk[0] * intensity;
let m = sep.cmyk[1] * intensity;
let y = sep.cmyk[2] * intensity;
let k = sep.cmyk[3] * intensity;
dst_slice[0] = (1.0 - dst_slice[0] - (1.0 - c) * (1.0 - k)).max(0.0);
dst_slice[1] = (1.0 - dst_slice[1] - (1.0 - m) * (1.0 - k)).max(0.0);
dst_slice[2] = (1.0 - dst_slice[2] - (1.0 - y) * (1.0 - k)).max(0.0);
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_clone_separations(_ctx: Handle, seps: Handle) -> Handle {
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(guard) = seps_arc.lock() {
let cloned = guard.clone();
return SEPARATIONS.insert(cloned);
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_separations_equal(_ctx: Handle, seps1: Handle, seps2: Handle) -> i32 {
if seps1 == seps2 {
return 1;
}
let s1 = SEPARATIONS.get(seps1);
let s2 = SEPARATIONS.get(seps2);
match (s1, s2) {
(Some(arc1), Some(arc2)) => {
let (g1, g2) = match (arc1.lock(), arc2.lock()) {
(Ok(g1), Ok(g2)) => (g1, g2),
_ => return 0,
};
if g1.seps.len() != g2.seps.len() {
return 0;
}
for (s1, s2) in g1.seps.iter().zip(g2.seps.iter()) {
if s1.name != s2.name || s1.cmyk != s2.cmyk {
return 0;
}
}
1
}
_ => 0,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_keep_separations(_ctx: Handle, seps: Handle) -> Handle {
SEPARATIONS.keep(seps)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_drop_separations(_ctx: Handle, seps: Handle) {
SEPARATIONS.remove(seps);
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_separations_have_spots(_ctx: Handle, seps: Handle) -> i32 {
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(guard) = seps_arc.lock() {
for sep in &guard.seps {
if sep.behavior == SeparationBehavior::Spot {
return 1;
}
}
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_count_active_separations(_ctx: Handle, seps: Handle) -> i32 {
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(guard) = seps_arc.lock() {
return guard
.seps
.iter()
.filter(|s| s.behavior != SeparationBehavior::Disabled)
.count() as i32;
}
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_all_separations_to_composite(_ctx: Handle, seps: Handle) {
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(mut guard) = seps_arc.lock() {
if !guard.controllable {
return;
}
for sep in &mut guard.seps {
sep.behavior = SeparationBehavior::Composite;
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_all_separations_to_spot(_ctx: Handle, seps: Handle) {
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(mut guard) = seps_arc.lock() {
if !guard.controllable {
return;
}
for sep in &mut guard.seps {
if !sep.is_none {
sep.behavior = SeparationBehavior::Spot;
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_disable_all_separations(_ctx: Handle, seps: Handle) {
if let Some(seps_arc) = SEPARATIONS.get(seps) {
if let Ok(mut guard) = seps_arc.lock() {
if !guard.controllable {
return;
}
for sep in &mut guard.seps {
sep.behavior = SeparationBehavior::Disabled;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_separations() {
let seps = fz_new_separations(0, 1);
assert!(seps > 0);
assert_eq!(fz_separations_controllable(0, seps), 1);
assert_eq!(fz_count_separations(0, seps), 0);
fz_drop_separations(0, seps);
}
#[test]
fn test_add_separation() {
let seps = fz_new_separations(0, 1);
let name = c"PANTONE 185 C";
let idx = fz_add_separation(0, seps, name.as_ptr(), 0, 0.0, 1.0, 0.9, 0.0);
assert_eq!(idx, 0);
assert_eq!(fz_count_separations(0, seps), 1);
assert_eq!(
fz_separation_current_behavior(0, seps, 0),
SeparationBehavior::Composite as i32
);
fz_drop_separations(0, seps);
}
#[test]
fn test_special_separations() {
let seps = fz_new_separations(0, 1);
let all_idx = fz_add_separation_all(0, seps);
let none_idx = fz_add_separation_none(0, seps);
assert_eq!(fz_count_separations(0, seps), 2);
assert_eq!(fz_separation_is_all(0, seps, all_idx), 1);
assert_eq!(fz_separation_is_none(0, seps, none_idx), 1);
fz_drop_separations(0, seps);
}
#[test]
fn test_separation_behavior() {
let seps = fz_new_separations(0, 1);
let name = c"Spot1";
fz_add_separation(0, seps, name.as_ptr(), 0, 1.0, 0.0, 0.0, 0.0);
assert_eq!(
fz_separation_current_behavior(0, seps, 0),
SeparationBehavior::Composite as i32
);
fz_set_separation_behavior(0, seps, 0, SeparationBehavior::Spot as i32);
assert_eq!(
fz_separation_current_behavior(0, seps, 0),
SeparationBehavior::Spot as i32
);
assert_eq!(fz_separations_have_spots(0, seps), 1);
fz_drop_separations(0, seps);
}
#[test]
fn test_non_controllable() {
let seps = fz_new_separations(0, 0);
let name = c"Spot1";
fz_add_separation(0, seps, name.as_ptr(), 0, 1.0, 0.0, 0.0, 0.0);
fz_set_separation_behavior(0, seps, 0, SeparationBehavior::Spot as i32);
assert_eq!(
fz_separation_current_behavior(0, seps, 0),
SeparationBehavior::Composite as i32
);
fz_drop_separations(0, seps);
}
#[test]
fn test_cmyk_equivalent() {
let seps = fz_new_separations(0, 1);
let name = c"Spot1";
fz_add_separation(0, seps, name.as_ptr(), 0, 0.1, 0.2, 0.3, 0.4);
let mut cmyk = [0.0f32; 4];
fz_separation_equivalent(0, seps, 0, cmyk.as_mut_ptr());
assert_eq!(cmyk, [0.1, 0.2, 0.3, 0.4]);
fz_drop_separations(0, seps);
}
#[test]
fn test_clone_separations() {
let seps = fz_new_separations(0, 1);
let name = c"Spot1";
fz_add_separation(0, seps, name.as_ptr(), 0, 1.0, 0.0, 0.0, 0.0);
let cloned = fz_clone_separations(0, seps);
assert!(cloned > 0);
assert_eq!(fz_count_separations(0, cloned), 1);
assert_eq!(fz_separations_equal(0, seps, cloned), 1);
fz_drop_separations(0, seps);
fz_drop_separations(0, cloned);
}
#[test]
fn test_bulk_behavior_changes() {
let seps = fz_new_separations(0, 1);
let name1 = c"Spot1";
let name2 = c"Spot2";
fz_add_separation(0, seps, name1.as_ptr(), 0, 1.0, 0.0, 0.0, 0.0);
fz_add_separation(0, seps, name2.as_ptr(), 0, 0.0, 1.0, 0.0, 0.0);
fz_set_all_separations_to_spot(0, seps);
assert_eq!(fz_separations_have_spots(0, seps), 1);
assert_eq!(fz_count_active_separations(0, seps), 2);
fz_disable_all_separations(0, seps);
assert_eq!(fz_count_active_separations(0, seps), 0);
fz_set_all_separations_to_composite(0, seps);
assert_eq!(fz_count_active_separations(0, seps), 2);
fz_drop_separations(0, seps);
}
}