use tiny_skia::BlendMode;
use crate::core::objects::Object;
pub(crate) fn pdf_blend_mode(name: &str) -> Option<BlendMode> {
match name {
"Normal" | "Compatible" => Some(BlendMode::SourceOver),
"Multiply" => Some(BlendMode::Multiply),
"Screen" => Some(BlendMode::Screen),
"Overlay" => Some(BlendMode::Overlay),
"Darken" => Some(BlendMode::Darken),
"Lighten" => Some(BlendMode::Lighten),
"ColorDodge" => Some(BlendMode::ColorDodge),
"ColorBurn" => Some(BlendMode::ColorBurn),
"HardLight" => Some(BlendMode::HardLight),
"SoftLight" => Some(BlendMode::SoftLight),
"Difference" => Some(BlendMode::Difference),
"Exclusion" => Some(BlendMode::Exclusion),
"Hue" => Some(BlendMode::Hue),
"Saturation" => Some(BlendMode::Saturation),
"Color" => Some(BlendMode::Color),
"Luminosity" => Some(BlendMode::Luminosity),
_ => None,
}
}
pub(crate) fn obj_f64(obj: &Object) -> Option<f64> {
match obj {
Object::Integer(i) => Some(*i as f64),
Object::Real(f) => Some(*f),
_ => None,
}
}
pub(crate) fn obj_f32(obj: &Object) -> Option<f32> {
obj_f64(obj).map(|v| v as f32)
}
pub(crate) fn op_f32(operands: &[&Object], idx: usize) -> Option<f32> {
operands.get(idx).and_then(|o| obj_f32(o))
}
pub(crate) fn set_gray(color: &mut tiny_skia::Color, g: f32) {
let c = g.clamp(0.0, 1.0);
if let Some(col) = tiny_skia::Color::from_rgba(c, c, c, 1.0) {
*color = col;
}
}
pub(crate) fn rgb_from_ops(operands: &[&Object]) -> Option<tiny_skia::Color> {
if operands.len() != 3 {
return None;
}
let r = op_f32(operands, 0)?;
let g = op_f32(operands, 1)?;
let b = op_f32(operands, 2)?;
tiny_skia::Color::from_rgba(r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0), 1.0)
}
pub(crate) fn cmyk_to_rgb(c: f32, m: f32, y: f32, k: f32) -> (f32, f32, f32) {
let r = (1.0 - c) * (1.0 - k);
let g = (1.0 - m) * (1.0 - k);
let b = (1.0 - y) * (1.0 - k);
(r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0))
}
pub(crate) fn cmyk_from_ops(operands: &[&Object]) -> Option<tiny_skia::Color> {
if operands.len() != 4 {
return None;
}
let (r, g, b) = cmyk_to_rgb(
op_f32(operands, 0)?,
op_f32(operands, 1)?,
op_f32(operands, 2)?,
op_f32(operands, 3)?,
);
tiny_skia::Color::from_rgba(r, g, b, 1.0)
}
pub(crate) fn set_color_from_operands(operands: &[&Object], color: &mut tiny_skia::Color) {
match operands.len() {
1 => {
if let Some(g) = op_f32(operands, 0) {
set_gray(color, g);
}
}
3 => {
if let Some(c) = rgb_from_ops(operands) {
*color = c;
}
}
4 => {
if let Some(c) = cmyk_from_ops(operands) {
*color = c;
}
}
_ => {}
}
}
pub(crate) fn parse_f32_array(obj: Option<&Object>) -> Option<Vec<f32>> {
let arr = obj?.as_array()?;
let values: Vec<f32> = arr.iter().filter_map(obj_f32).collect();
if values.is_empty() {
None
} else {
Some(values)
}
}
pub(crate) fn parse_transform(obj: Option<&Object>) -> Option<tiny_skia::Transform> {
let arr = obj?.as_array()?;
if arr.len() >= 6 {
Some(tiny_skia::Transform::from_row(
obj_f32(&arr[0])?,
obj_f32(&arr[1])?,
obj_f32(&arr[2])?,
obj_f32(&arr[3])?,
obj_f32(&arr[4])?,
obj_f32(&arr[5])?,
))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blend_mode_normal() {
assert_eq!(pdf_blend_mode("Normal"), Some(BlendMode::SourceOver));
}
#[test]
fn blend_mode_compatible_alias() {
assert_eq!(pdf_blend_mode("Compatible"), Some(BlendMode::SourceOver));
}
#[test]
fn blend_mode_multiply() {
assert_eq!(pdf_blend_mode("Multiply"), Some(BlendMode::Multiply));
}
#[test]
fn blend_mode_unknown() {
assert!(pdf_blend_mode("FancyBlend").is_none());
}
#[test]
fn obj_f64_integer() {
assert_eq!(obj_f64(&Object::Integer(42)), Some(42.0));
}
#[test]
fn obj_f64_real() {
assert_eq!(obj_f64(&Object::Real(3.14)), Some(3.14));
}
#[test]
fn obj_f64_non_numeric() {
assert!(obj_f64(&Object::Boolean(true)).is_none());
}
#[test]
fn obj_f32_from_integer() {
assert_eq!(obj_f32(&Object::Integer(7)), Some(7.0f32));
}
#[test]
fn op_f32_valid_index() {
let ops: Vec<Object> = vec![Object::Real(1.5), Object::Integer(3)];
let refs: Vec<&Object> = ops.iter().collect();
assert_eq!(op_f32(&refs, 0), Some(1.5));
assert_eq!(op_f32(&refs, 1), Some(3.0));
}
#[test]
fn op_f32_out_of_bounds() {
let ops: Vec<Object> = vec![Object::Real(1.0)];
let refs: Vec<&Object> = ops.iter().collect();
assert!(op_f32(&refs, 5).is_none());
}
#[test]
fn set_gray_midtone() {
let mut color = tiny_skia::Color::BLACK;
set_gray(&mut color, 0.5);
assert!((color.red() - 0.5).abs() < 0.01);
assert!((color.green() - 0.5).abs() < 0.01);
assert!((color.blue() - 0.5).abs() < 0.01);
}
#[test]
fn set_gray_clamps_negative() {
let mut color = tiny_skia::Color::WHITE;
set_gray(&mut color, -0.5);
assert!(color.red() < 0.01);
}
#[test]
fn rgb_from_ops_valid() {
let ops = vec![Object::Real(1.0), Object::Real(0.0), Object::Real(0.5)];
let refs: Vec<&Object> = ops.iter().collect();
let c = rgb_from_ops(&refs).unwrap();
assert!((c.red() - 1.0).abs() < 0.01);
assert!(c.green() < 0.01);
assert!((c.blue() - 0.5).abs() < 0.01);
}
#[test]
fn rgb_from_ops_wrong_count() {
let ops = vec![Object::Real(1.0), Object::Real(0.0)];
let refs: Vec<&Object> = ops.iter().collect();
assert!(rgb_from_ops(&refs).is_none());
}
#[test]
fn cmyk_to_rgb_pure_cyan() {
let (r, g, b) = cmyk_to_rgb(1.0, 0.0, 0.0, 0.0);
assert!(r < 0.01); assert!((g - 1.0).abs() < 0.01);
assert!((b - 1.0).abs() < 0.01);
}
#[test]
fn cmyk_to_rgb_full_black() {
let (r, g, b) = cmyk_to_rgb(0.0, 0.0, 0.0, 1.0);
assert!(r < 0.01);
assert!(g < 0.01);
assert!(b < 0.01);
}
#[test]
fn cmyk_to_rgb_no_ink() {
let (r, g, b) = cmyk_to_rgb(0.0, 0.0, 0.0, 0.0);
assert!((r - 1.0).abs() < 0.01);
assert!((g - 1.0).abs() < 0.01);
assert!((b - 1.0).abs() < 0.01);
}
#[test]
fn cmyk_from_ops_valid() {
let ops = vec![
Object::Real(0.0),
Object::Real(1.0),
Object::Real(1.0),
Object::Real(0.0),
];
let refs: Vec<&Object> = ops.iter().collect();
let c = cmyk_from_ops(&refs).unwrap();
assert!((c.red() - 1.0).abs() < 0.01);
assert!(c.green() < 0.01);
assert!(c.blue() < 0.01);
}
#[test]
fn cmyk_from_ops_wrong_count() {
let ops = vec![Object::Real(0.0), Object::Real(0.0)];
let refs: Vec<&Object> = ops.iter().collect();
assert!(cmyk_from_ops(&refs).is_none());
}
#[test]
fn set_color_gray_operand() {
let ops = vec![Object::Real(0.75)];
let refs: Vec<&Object> = ops.iter().collect();
let mut color = tiny_skia::Color::BLACK;
set_color_from_operands(&refs, &mut color);
assert!((color.red() - 0.75).abs() < 0.01);
}
#[test]
fn set_color_rgb_operands() {
let ops = vec![Object::Real(0.0), Object::Real(1.0), Object::Real(0.0)];
let refs: Vec<&Object> = ops.iter().collect();
let mut color = tiny_skia::Color::BLACK;
set_color_from_operands(&refs, &mut color);
assert!(color.red() < 0.01);
assert!((color.green() - 1.0).abs() < 0.01);
}
#[test]
fn set_color_unsupported_count_noop() {
let ops = vec![Object::Real(0.0), Object::Real(0.0)];
let refs: Vec<&Object> = ops.iter().collect();
let mut color = tiny_skia::Color::WHITE;
set_color_from_operands(&refs, &mut color);
assert!((color.red() - 1.0).abs() < 0.01);
}
}