use crate::content::color_space::{
ResolvedColorSpace, components_to_device_color_icc, painted_channels_for_cs,
resolve_color_space_obj,
};
use crate::content::graphics_state::PdfGraphicsState;
use crate::error::PdfError;
use crate::objects::{PdfDict, PdfObj};
use crate::resolver::Resolver;
use crate::resources::function::PdfFunction;
use std::sync::Arc;
use stet_fonts::geometry::Matrix;
use stet_graphics::device::{
AxialShadingParams, ColorStop, ImageColorSpace, ImageParams, MeshShadingParams,
PatchShadingParams, RadialShadingParams, ShadingColorSpace, SimpleColorSpace, SpotTintFunction,
};
use stet_graphics::display_list::{DisplayElement, DisplayList};
use stet_graphics::icc::IccCache;
pub fn handle_shading(
shading_obj: &PdfObj,
dict: &PdfDict,
gstate: &PdfGraphicsState,
resolver: &Resolver,
display_list: &mut DisplayList,
icc_cache: &mut IccCache,
) -> Result<(), PdfError> {
let shading_type =
dict.get_int(b"ShadingType")
.ok_or(PdfError::Other("shading missing ShadingType".into()))? as i32;
let bbox = parse_bbox(dict);
let extend = parse_extend(dict);
let resolved_cs = resolve_shading_resolved_cs(dict, resolver);
if let Some(bg_arr) = dict.get_array(b"Background") {
let comps: Vec<f64> = bg_arr.iter().filter_map(|o| o.as_f64()).collect();
let bg_color = components_to_device_color_icc(&resolved_cs, &comps, Some(icc_cache));
let mut params = gstate.fill_params(stet_graphics::color::FillRule::NonZeroWinding);
params.color = bg_color;
let mut path = stet_fonts::geometry::PsPath::new();
path.segments
.push(stet_fonts::geometry::PathSegment::MoveTo(-1e6, -1e6));
path.segments
.push(stet_fonts::geometry::PathSegment::LineTo(1e6, -1e6));
path.segments
.push(stet_fonts::geometry::PathSegment::LineTo(1e6, 1e6));
path.segments
.push(stet_fonts::geometry::PathSegment::LineTo(-1e6, 1e6));
path.segments
.push(stet_fonts::geometry::PathSegment::ClosePath);
display_list.push(DisplayElement::Fill { path, params });
}
match shading_type {
1 => handle_function_based(
dict,
gstate,
resolver,
display_list,
&resolved_cs,
icc_cache,
),
2 => handle_axial(
dict,
gstate,
resolver,
display_list,
bbox,
extend,
&resolved_cs,
icc_cache,
),
3 => handle_radial(
dict,
gstate,
resolver,
display_list,
bbox,
extend,
&resolved_cs,
icc_cache,
),
4 | 5 => handle_mesh(
shading_obj,
dict,
gstate,
resolver,
display_list,
shading_type,
&resolved_cs,
icc_cache,
),
6 | 7 => handle_patches(
shading_obj,
dict,
gstate,
resolver,
display_list,
shading_type,
&resolved_cs,
icc_cache,
),
_ => Ok(()),
}
}
fn handle_function_based(
dict: &PdfDict,
gstate: &PdfGraphicsState,
resolver: &Resolver,
display_list: &mut DisplayList,
resolved_cs: &ResolvedColorSpace,
icc_cache: &mut IccCache,
) -> Result<(), PdfError> {
let function = parse_shading_function(dict, resolver)?;
let domain = dict
.get_array(b"Domain")
.map(|a| {
let v: Vec<f64> = a.iter().filter_map(|o| o.as_f64()).collect();
if v.len() >= 4 {
[v[0], v[1], v[2], v[3]]
} else {
[0.0, 1.0, 0.0, 1.0]
}
})
.unwrap_or([0.0, 1.0, 0.0, 1.0]);
let shading_matrix = dict
.get_array(b"Matrix")
.map(|a| {
let v: Vec<f64> = a.iter().filter_map(|o| o.as_f64()).collect();
if v.len() >= 6 {
Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5])
} else {
Matrix::identity()
}
})
.unwrap_or_else(Matrix::identity);
let domain_w = domain[1] - domain[0];
let domain_h = domain[3] - domain[2];
let domain_matrix = Matrix::new(domain_w, 0.0, 0.0, domain_h, domain[0], domain[2]);
let combined = gstate.ctm.concat(&shading_matrix).concat(&domain_matrix);
let dev_w = (combined.a * combined.a + combined.b * combined.b).sqrt();
let dev_h = (combined.c * combined.c + combined.d * combined.d).sqrt();
let width = (dev_w.ceil() as u32).clamp(2, 2048);
let height = (dev_h.ceil() as u32).clamp(2, 2048);
let mut rgba = vec![255u8; (width * height * 4) as usize];
for row in 0..height {
for col in 0..width {
let x = domain[0] + (col as f64 + 0.5) / width as f64 * (domain[1] - domain[0]);
let y = domain[3] - (row as f64 + 0.5) / height as f64 * (domain[3] - domain[2]);
let components = function.evaluate(&[x, y]);
let color = components_to_device_color_icc(resolved_cs, &components, Some(icc_cache));
let idx = ((row * width + col) * 4) as usize;
rgba[idx] = (color.r * 255.0 + 0.5) as u8;
rgba[idx + 1] = (color.g * 255.0 + 0.5) as u8;
rgba[idx + 2] = (color.b * 255.0 + 0.5) as u8;
}
}
let image_matrix = Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
display_list.push(DisplayElement::Image {
sample_data: std::sync::Arc::new(rgba),
params: ImageParams {
width,
height,
color_space: ImageColorSpace::PreconvertedRGBA,
bits_per_component: 8,
ctm: combined,
image_matrix,
interpolate: true,
mask_color: None,
alpha: 1.0,
blend_mode: 0,
overprint: false,
overprint_mode: 0,
opm_paired: false,
painted_channels: 0,
alpha_is_shape: false,
rendering_intent: 0,
},
});
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn handle_axial(
dict: &PdfDict,
gstate: &PdfGraphicsState,
resolver: &Resolver,
display_list: &mut DisplayList,
bbox: Option<[f64; 4]>,
extend: (bool, bool),
resolved_cs: &ResolvedColorSpace,
icc_cache: &mut IccCache,
) -> Result<(), PdfError> {
let coords = dict
.get_array(b"Coords")
.ok_or(PdfError::Other("axial shading missing Coords".into()))?;
let vals: Vec<f64> = coords.iter().filter_map(|o| o.as_f64()).collect();
if vals.len() < 4 {
return Err(PdfError::Other("axial Coords needs 4 values".into()));
}
let function = parse_shading_function(dict, resolver)?;
let n_stops = function.min_samples().max(64).min(1024);
let color_stops = sample_function_to_stops_icc(&function, n_stops, resolved_cs, icc_cache);
let cs = resolved_cs_to_shading_cs(resolved_cs);
display_list.push(DisplayElement::AxialShading {
params: AxialShadingParams {
x0: vals[0],
y0: vals[1],
x1: vals[2],
y1: vals[3],
color_stops,
extend_start: extend.0,
extend_end: extend.1,
ctm: gstate.ctm,
bbox,
color_space: cs,
overprint: gstate.overprint,
overprint_mode: gstate.overprint_mode,
painted_channels: painted_channels_for_cs(resolved_cs),
alpha: gstate.fill_alpha,
blend_mode: gstate.blend_mode,
alpha_is_shape: gstate.alpha_is_shape,
spot_tint_blend: cs_has_spot_with_cmyk_alt(resolved_cs),
},
});
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn handle_radial(
dict: &PdfDict,
gstate: &PdfGraphicsState,
resolver: &Resolver,
display_list: &mut DisplayList,
bbox: Option<[f64; 4]>,
extend: (bool, bool),
resolved_cs: &ResolvedColorSpace,
icc_cache: &mut IccCache,
) -> Result<(), PdfError> {
let coords = dict
.get_array(b"Coords")
.ok_or(PdfError::Other("radial shading missing Coords".into()))?;
let vals: Vec<f64> = coords.iter().filter_map(|o| o.as_f64()).collect();
if vals.len() < 6 {
return Err(PdfError::Other("radial Coords needs 6 values".into()));
}
let function = parse_shading_function(dict, resolver)?;
let n_stops = function.min_samples().max(64).min(1024);
let color_stops = sample_function_to_stops_icc(&function, n_stops, resolved_cs, icc_cache);
let cs = resolved_cs_to_shading_cs(resolved_cs);
display_list.push(DisplayElement::RadialShading {
params: RadialShadingParams {
x0: vals[0],
y0: vals[1],
r0: vals[2],
x1: vals[3],
y1: vals[4],
r1: vals[5],
color_stops,
extend_start: extend.0,
extend_end: extend.1,
ctm: gstate.ctm,
bbox,
color_space: cs,
overprint: gstate.overprint,
overprint_mode: gstate.overprint_mode,
painted_channels: painted_channels_for_cs(resolved_cs),
alpha: gstate.fill_alpha,
blend_mode: gstate.blend_mode,
alpha_is_shape: gstate.alpha_is_shape,
spot_tint_blend: cs_has_spot_with_cmyk_alt(resolved_cs),
},
});
Ok(())
}
fn cs_has_spot_with_cmyk_alt(cs: &ResolvedColorSpace) -> bool {
use stet_graphics::device::cmyk_channel_for_name;
match cs {
ResolvedColorSpace::Separation { name, alt, .. } => {
cmyk_channel_for_name(name) == 0
&& matches!(alt.as_ref(), ResolvedColorSpace::DeviceCMYK)
}
ResolvedColorSpace::DeviceN { names, alt, .. } => {
matches!(alt.as_ref(), ResolvedColorSpace::DeviceCMYK)
&& names.iter().any(|n| cmyk_channel_for_name(n) == 0)
}
_ => false,
}
}
#[allow(clippy::too_many_arguments)]
fn handle_mesh(
shading_obj: &PdfObj,
dict: &PdfDict,
gstate: &PdfGraphicsState,
resolver: &Resolver,
display_list: &mut DisplayList,
shading_type: i32,
resolved_cs: &ResolvedColorSpace,
icc_cache: &mut IccCache,
) -> Result<(), PdfError> {
let bpc = dict.get_int(b"BitsPerCoordinate").unwrap_or(8) as usize;
let bpco = dict.get_int(b"BitsPerComponent").unwrap_or(8) as usize;
let bpfl = dict.get_int(b"BitsPerFlag").unwrap_or(8) as usize;
let decode = dict
.get_array(b"Decode")
.map(|a| a.iter().filter_map(|o| o.as_f64()).collect::<Vec<_>>())
.unwrap_or_default();
let cs = resolved_cs_to_shading_cs(resolved_cs);
let cs_comps = resolved_cs.num_components();
let function = if dict.get(b"Function").is_some() {
parse_shading_function(dict, resolver).ok()
} else {
None
};
let n_comps = if function.is_some() {
let color_entries = decode.len().saturating_sub(4);
(color_entries / 2).max(1)
} else {
cs_comps
};
let data = resolver.stream_data_from_obj(shading_obj)?;
let mut triangles = match shading_type {
4 => {
stet_graphics::mesh_shading::parse_type4_mesh(&data, bpc, bpco, bpfl, &decode, n_comps)
}
5 => {
let vpr = dict.get_int(b"VerticesPerRow").unwrap_or(2) as usize;
stet_graphics::mesh_shading::parse_type5_mesh(&data, bpc, bpco, &decode, n_comps, vpr)
}
_ => return Ok(()),
};
let color_lut = if let Some(ref func) = function {
if n_comps == 1 {
let d_min = decode.get(4).copied().unwrap_or(0.0);
let d_max = decode.get(5).copied().unwrap_or(1.0);
let d_range = (d_max - d_min).abs().max(1e-10);
let lut_size = 256;
let mut lut = Vec::with_capacity(lut_size);
for i in 0..lut_size {
let t = i as f64 / (lut_size - 1) as f64;
let input = d_min + t * (d_max - d_min);
let components = func.evaluate(&[input]);
let color =
components_to_device_color_icc(resolved_cs, &components, Some(icc_cache));
lut.push(color);
}
for t in &mut triangles {
for v in [&mut t.v0, &mut t.v1, &mut t.v2] {
let raw = v.raw_components[0];
let normalized = ((raw - d_min) / d_range).clamp(0.0, 1.0);
v.raw_components = vec![normalized];
}
}
Some(std::sync::Arc::new(lut))
} else {
None
}
} else {
None
};
if let Some(ref func) = function {
if color_lut.is_some() {
let d_min = decode.get(4).copied().unwrap_or(0.0);
let d_max = decode.get(5).copied().unwrap_or(1.0);
for t in &mut triangles {
for v in [&mut t.v0, &mut t.v1, &mut t.v2] {
let input = d_min + v.raw_components[0] * (d_max - d_min);
let expanded = func.evaluate(&[input]);
let color =
components_to_device_color_icc(resolved_cs, &expanded, Some(icc_cache));
v.color = color;
}
}
} else {
for t in &mut triangles {
t.v0.raw_components = func.evaluate(&t.v0.raw_components);
t.v1.raw_components = func.evaluate(&t.v1.raw_components);
t.v2.raw_components = func.evaluate(&t.v2.raw_components);
}
}
}
if color_lut.is_none() {
for t in &mut triangles {
t.v0.color =
components_to_device_color_icc(resolved_cs, &t.v0.raw_components, Some(icc_cache));
t.v1.color =
components_to_device_color_icc(resolved_cs, &t.v1.raw_components, Some(icc_cache));
t.v2.color =
components_to_device_color_icc(resolved_cs, &t.v2.raw_components, Some(icc_cache));
}
}
for t in &mut triangles {
let (x, y) = gstate.ctm.transform_point(t.v0.x, t.v0.y);
t.v0.x = x;
t.v0.y = y;
let (x, y) = gstate.ctm.transform_point(t.v1.x, t.v1.y);
t.v1.x = x;
t.v1.y = y;
let (x, y) = gstate.ctm.transform_point(t.v2.x, t.v2.y);
t.v2.x = x;
t.v2.y = y;
}
let bbox = parse_bbox(dict);
let device_bbox = transform_bbox(&bbox, &gstate.ctm);
display_list.push(DisplayElement::MeshShading {
params: MeshShadingParams {
triangles,
ctm: Matrix::identity(),
bbox: device_bbox,
color_space: cs,
overprint: gstate.overprint,
overprint_mode: gstate.overprint_mode,
painted_channels: painted_channels_for_cs(resolved_cs),
color_lut,
alpha: gstate.fill_alpha,
blend_mode: gstate.blend_mode,
alpha_is_shape: gstate.alpha_is_shape,
},
});
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn handle_patches(
shading_obj: &PdfObj,
dict: &PdfDict,
gstate: &PdfGraphicsState,
resolver: &Resolver,
display_list: &mut DisplayList,
shading_type: i32,
resolved_cs: &ResolvedColorSpace,
icc_cache: &mut IccCache,
) -> Result<(), PdfError> {
let bpc = dict.get_int(b"BitsPerCoordinate").unwrap_or(8) as usize;
let bpco = dict.get_int(b"BitsPerComponent").unwrap_or(8) as usize;
let bpfl = dict.get_int(b"BitsPerFlag").unwrap_or(8) as usize;
let decode = dict
.get_array(b"Decode")
.map(|a| a.iter().filter_map(|o| o.as_f64()).collect::<Vec<_>>())
.unwrap_or_default();
let cs = resolved_cs_to_shading_cs(resolved_cs);
let cs_comps = resolved_cs.num_components();
let function = if dict.get(b"Function").is_some() {
parse_shading_function(dict, resolver).ok()
} else {
None
};
let n_comps = if function.is_some() {
let color_entries = decode.len().saturating_sub(4);
(color_entries / 2).max(1)
} else {
cs_comps
};
let data = resolver.stream_data_from_obj(shading_obj)?;
let mut patches = match shading_type {
6 => stet_graphics::mesh_shading::parse_type6_patches(
&data, bpc, bpco, bpfl, &decode, n_comps,
),
7 => stet_graphics::mesh_shading::parse_type7_patches(
&data, bpc, bpco, bpfl, &decode, n_comps,
),
_ => return Ok(()),
};
let color_lut = if let Some(ref func) = function {
if n_comps == 1 {
let d_min = decode.get(4).copied().unwrap_or(0.0);
let d_max = decode.get(5).copied().unwrap_or(1.0);
let d_range = (d_max - d_min).abs().max(1e-10);
let lut_size = 256;
let mut lut = Vec::with_capacity(lut_size);
for i in 0..lut_size {
let t = i as f64 / (lut_size - 1) as f64;
let input = d_min + t * (d_max - d_min);
let components = func.evaluate(&[input]);
let color =
components_to_device_color_icc(resolved_cs, &components, Some(icc_cache));
lut.push(color);
}
for p in &mut patches {
for i in 0..4 {
let raw = p.raw_colors[i][0];
let normalized = ((raw - d_min) / d_range).clamp(0.0, 1.0);
p.raw_colors[i] = vec![normalized];
let idx = (normalized * 255.0).round() as usize;
p.colors[i] = lut[idx.min(255)].clone();
}
}
Some(std::sync::Arc::new(lut))
} else {
for p in &mut patches {
for i in 0..4 {
p.raw_colors[i] = func.evaluate(&p.raw_colors[i]);
}
}
for p in &mut patches {
for i in 0..4 {
p.colors[i] = components_to_device_color_icc(
resolved_cs,
&p.raw_colors[i],
Some(icc_cache),
);
}
}
None
}
} else {
for p in &mut patches {
for i in 0..4 {
p.colors[i] =
components_to_device_color_icc(resolved_cs, &p.raw_colors[i], Some(icc_cache));
}
}
None
};
for p in &mut patches {
for pt in &mut p.points {
let (x, y) = gstate.ctm.transform_point(pt.0, pt.1);
pt.0 = x;
pt.1 = y;
}
}
let bbox = parse_bbox(dict);
let device_bbox = transform_bbox(&bbox, &gstate.ctm);
display_list.push(DisplayElement::PatchShading {
params: PatchShadingParams {
patches,
ctm: Matrix::identity(),
bbox: device_bbox,
color_space: cs,
overprint: gstate.overprint,
overprint_mode: gstate.overprint_mode,
painted_channels: painted_channels_for_cs(resolved_cs),
color_lut,
alpha: gstate.fill_alpha,
blend_mode: gstate.blend_mode,
alpha_is_shape: gstate.alpha_is_shape,
},
});
Ok(())
}
fn parse_shading_function(dict: &PdfDict, resolver: &Resolver) -> Result<PdfFunction, PdfError> {
let fn_obj = dict
.get(b"Function")
.ok_or(PdfError::Other("shading missing Function".into()))?;
let fn_obj = resolver.deref(fn_obj)?;
if matches!(fn_obj, PdfObj::Null) {
return Err(PdfError::Other("shading Function is null".into()));
}
if let PdfObj::Array(arr) = &fn_obj {
if arr.len() == 1 {
return PdfFunction::parse(&arr[0], resolver);
}
if arr.len() > 1 {
let mut funcs = Vec::with_capacity(arr.len());
for item in arr {
funcs.push(PdfFunction::parse(item, resolver)?);
}
return Ok(PdfFunction::composite(funcs));
}
}
PdfFunction::parse(&fn_obj, resolver)
}
fn sample_function_to_stops_icc(
function: &PdfFunction,
n_samples: usize,
resolved_cs: &ResolvedColorSpace,
icc_cache: &mut IccCache,
) -> Vec<ColorStop> {
let cmyk_tint_fn = match resolved_cs {
ResolvedColorSpace::Separation { alt, tint_fn, .. }
| ResolvedColorSpace::DeviceN { alt, tint_fn, .. }
if matches!(**alt, ResolvedColorSpace::DeviceCMYK) =>
{
tint_fn.as_ref()
}
_ => None,
};
let [d_min, d_max] = function.domain_0();
let span = d_max - d_min;
let disc_positions = function.discontinuity_positions();
let mut disc_ts: Vec<f64> = disc_positions
.iter()
.filter_map(|&d| {
if span.abs() < 1e-15 {
return None;
}
let t = (d - d_min) / span;
if t > 0.0 && t < 1.0 { Some(t) } else { None }
})
.collect();
disc_ts.sort_by(f64::total_cmp);
disc_ts.dedup_by(|a, b| (*a - *b).abs() < 1e-12);
let n_samples = n_samples.max(2);
let mut sample_ts: Vec<f64> = (0..n_samples)
.map(|i| i as f64 / (n_samples - 1) as f64)
.collect();
let eps = 1e-10;
for &dt in &disc_ts {
sample_ts.push((dt - eps).max(0.0));
sample_ts.push(dt);
}
sample_ts.sort_by(f64::total_cmp);
sample_ts.dedup_by(|a, b| (*a - *b).abs() < 1e-14);
let is_spot_with_cmyk_alt = cmyk_tint_fn.is_some();
let mut stops = Vec::with_capacity(sample_ts.len());
for t in sample_ts {
let input = d_min + t * span;
let components = function.evaluate(&[input]);
let color = components_to_device_color_icc(resolved_cs, &components, Some(icc_cache));
let (raw_components, source_components) = if let Some(tint) = cmyk_tint_fn {
let cmyk = tint.evaluate(&components);
let raw = if cmyk.len() >= 4 {
cmyk[..4].to_vec()
} else {
components.clone()
};
(raw, components)
} else if is_spot_with_cmyk_alt {
(components.clone(), components)
} else {
(components, Vec::new())
};
stops.push(ColorStop {
position: t,
color,
raw_components,
source_components,
});
}
stops
}
fn transform_bbox(bbox: &Option<[f64; 4]>, ctm: &Matrix) -> Option<[f64; 4]> {
bbox.map(|b| {
let corners = [
ctm.transform_point(b[0], b[1]),
ctm.transform_point(b[2], b[1]),
ctm.transform_point(b[0], b[3]),
ctm.transform_point(b[2], b[3]),
];
let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
let x_max = corners
.iter()
.map(|c| c.0)
.fold(f64::NEG_INFINITY, f64::max);
let y_max = corners
.iter()
.map(|c| c.1)
.fold(f64::NEG_INFINITY, f64::max);
[x_min, y_min, x_max, y_max]
})
}
fn parse_bbox(dict: &PdfDict) -> Option<[f64; 4]> {
dict.get_array(b"BBox").and_then(|arr| {
let vals: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
if vals.len() == 4 {
Some([vals[0], vals[1], vals[2], vals[3]])
} else {
None
}
})
}
fn parse_extend(dict: &PdfDict) -> (bool, bool) {
dict.get_array(b"Extend")
.and_then(|arr| {
if arr.len() == 2 {
let a = matches!(arr[0], PdfObj::Bool(true));
let b = matches!(arr[1], PdfObj::Bool(true));
Some((a, b))
} else {
None
}
})
.unwrap_or((false, false))
}
fn resolve_shading_resolved_cs(dict: &PdfDict, resolver: &Resolver) -> ResolvedColorSpace {
if let Some(cs_obj) = dict.get(b"ColorSpace")
&& let Ok(resolved) = resolve_color_space_obj(cs_obj, resolver)
{
return resolved;
}
ResolvedColorSpace::DeviceRGB
}
fn resolved_cs_to_shading_cs(cs: &ResolvedColorSpace) -> ShadingColorSpace {
match cs {
ResolvedColorSpace::DeviceGray => ShadingColorSpace::DeviceGray,
ResolvedColorSpace::DeviceRGB => ShadingColorSpace::DeviceRGB,
ResolvedColorSpace::DeviceCMYK => ShadingColorSpace::DeviceCMYK,
ResolvedColorSpace::ICCBased {
n,
profile_hash: Some(hash),
profile_data: Some(data),
..
} if *n != 1 && *n != 4 => ShadingColorSpace::ICCBased {
n: *n as u32,
profile_hash: *hash,
profile_data: Arc::clone(data),
},
ResolvedColorSpace::ICCBased { n, .. } => match n {
1 => ShadingColorSpace::DeviceGray,
4 => ShadingColorSpace::DeviceCMYK,
_ => ShadingColorSpace::DeviceRGB,
},
ResolvedColorSpace::Indexed { base, .. } => resolved_cs_to_shading_cs(base),
ResolvedColorSpace::Separation { name, alt, tint_fn }
if matches!(**alt, ResolvedColorSpace::DeviceCMYK) =>
{
match tint_fn {
Some(tint) => ShadingColorSpace::Separation {
name: name.clone(),
alternate: SimpleColorSpace::DeviceCMYK,
tint_function: sample_tint_function_1d(tint, 16),
},
None => ShadingColorSpace::DeviceCMYK,
}
}
ResolvedColorSpace::DeviceN {
names,
alt,
tint_fn,
} if matches!(**alt, ResolvedColorSpace::DeviceCMYK) => match tint_fn {
Some(tint) => ShadingColorSpace::DeviceN {
names: names.clone(),
alternate: SimpleColorSpace::DeviceCMYK,
tint_function: sample_tint_function_nd(tint, names.len(), 8),
},
None => ShadingColorSpace::DeviceCMYK,
},
_ => ShadingColorSpace::DeviceRGB,
}
}
fn sample_tint_function_1d(tint: &PdfFunction, samples_per_dim: usize) -> SpotTintFunction {
let mut cmyk_samples = Vec::with_capacity(samples_per_dim * 4);
for i in 0..samples_per_dim {
let t = i as f64 / (samples_per_dim - 1).max(1) as f64;
let out = tint.evaluate(&[t]);
for k in 0..4 {
cmyk_samples.push(out.get(k).copied().unwrap_or(0.0));
}
}
SpotTintFunction {
input_dim: 1,
samples_per_dim,
cmyk_samples: Arc::new(cmyk_samples),
}
}
fn sample_tint_function_nd(
tint: &PdfFunction,
input_dim: usize,
samples_per_dim: usize,
) -> SpotTintFunction {
let total = samples_per_dim.pow(input_dim as u32);
let mut cmyk_samples = Vec::with_capacity(total * 4);
let mut input = vec![0.0_f64; input_dim];
for idx in 0..total {
let mut rem = idx;
for axis in (0..input_dim).rev() {
let i = rem % samples_per_dim;
rem /= samples_per_dim;
input[axis] = i as f64 / (samples_per_dim - 1).max(1) as f64;
}
let out = tint.evaluate(&input);
for k in 0..4 {
cmyk_samples.push(out.get(k).copied().unwrap_or(0.0));
}
}
SpotTintFunction {
input_dim,
samples_per_dim,
cmyk_samples: Arc::new(cmyk_samples),
}
}