use crate::cache::Cache;
use crate::color::{Color, ColorComponents, ColorSpace};
use crate::context::Context;
use crate::device::Device;
use crate::function::Function;
use crate::interpret::state::State;
use crate::util::hash128;
use crate::x_object::{FormXObject, draw_form_xobject};
use crate::{CacheKey, InterpreterSettings};
use kurbo::Affine;
use pdf_syntax::object::Name;
use pdf_syntax::object::ObjectIdentifier;
use pdf_syntax::object::Stream;
use pdf_syntax::object::dict::keys::*;
use pdf_syntax::object::{Dict, Object};
use pdf_syntax::page::Resources;
use pdf_syntax::xref::XRef;
use smallvec::smallvec;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::Arc;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MaskType {
Luminosity,
Alpha,
}
pub struct TransferFunction(Function);
impl TransferFunction {
#[inline]
pub fn apply(&self, val: f32) -> f32 {
self.0
.eval(smallvec![val])
.and_then(|v| v.first().copied())
.unwrap_or(0.0)
.clamp(0.0, 1.0)
}
}
struct Repr<'a> {
obj_id: ObjectIdentifier,
group: FormXObject<'a>,
mask_type: MaskType,
parent_resources: Resources<'a>,
root_transform: Affine,
bbox: kurbo::Rect,
object_cache: Cache,
transfer_function: Option<TransferFunction>,
settings: InterpreterSettings,
background: Color,
xref: &'a XRef,
}
impl Hash for Repr<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.obj_id.hash(state);
self.root_transform.cache_key().hash(state);
}
}
#[derive(Clone, Hash)]
pub struct SoftMask<'a>(Arc<Repr<'a>>);
impl Debug for SoftMask<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SoftMask({:?})", self.0.obj_id)
}
}
impl PartialEq for SoftMask<'_> {
fn eq(&self, other: &Self) -> bool {
self.0.obj_id == other.0.obj_id
}
}
impl Eq for SoftMask<'_> {}
impl CacheKey for SoftMask<'_> {
fn cache_key(&self) -> u128 {
hash128(self)
}
}
impl<'a> SoftMask<'a> {
pub(crate) fn new(
dict: &Dict<'a>,
context: &Context<'a>,
parent_resources: Resources<'a>,
) -> Option<Self> {
let obj_id = dict.get_ref(G)?.into();
let group_stream = dict.get::<Stream<'_>>(G)?;
let group = FormXObject::new(&group_stream)?;
let cs = ColorSpace::new(
group.dict.get::<Dict<'_>>(GROUP)?.get::<Object<'_>>(CS)?,
&context.object_cache,
)?;
let transfer_function = dict
.get::<Object<'_>>(TR)
.and_then(|o| Function::new(&o))
.map(TransferFunction);
let (mask_type, background) = match dict.get::<Name>(S)?.deref() {
LUMINOSITY => {
let color = dict
.get::<ColorComponents>(BC)
.map(|c| Color::new(cs, c, 1.0))
.unwrap_or(Color::new(ColorSpace::device_gray(), smallvec![0.0], 1.0));
(MaskType::Luminosity, color)
}
ALPHA => (
MaskType::Alpha,
Color::new(ColorSpace::device_gray(), smallvec![0.0], 1.0),
),
_ => return None,
};
Some(Self(Arc::new(Repr {
obj_id,
group,
mask_type,
root_transform: context.get().ctm,
transfer_function,
bbox: context.bbox(),
object_cache: context.object_cache.clone(),
settings: context.settings.clone(),
xref: context.xref,
background,
parent_resources,
})))
}
pub fn interpret(&self, device: &mut impl Device<'a>) {
let state = State::new(self.0.root_transform);
let mut ctx = Context::new_with(
self.0.root_transform,
self.0.bbox,
self.0.object_cache.clone(),
self.0.xref,
self.0.settings.clone(),
state,
);
draw_form_xobject(&self.0.parent_resources, &self.0.group, &mut ctx, device);
}
pub fn id(&self) -> ObjectIdentifier {
self.0.obj_id
}
pub fn mask_type(&self) -> MaskType {
self.0.mask_type
}
pub fn background_color(&self) -> Color {
self.0.background.clone()
}
pub fn transfer_function(&self) -> Option<&TransferFunction> {
self.0.transfer_function.as_ref()
}
}