use std::hash::{Hash, Hasher};
use std::sync::Arc;
use pdf_writer::{Obj, Ref, Str};
use tiny_skia_path::Transform;
use crate::chunk_container::ChunkContainer;
use crate::error::{KrillaError, KrillaResult};
use crate::geom::Point;
use crate::serialize::{PageInfo, SerializeContext};
#[derive(Hash)]
pub enum Destination {
Xyz(XyzDestination),
Named(NamedDestination),
}
impl Destination {
pub(crate) fn serialize(&self, sc: &mut SerializeContext, buffer: Obj) -> KrillaResult<()> {
match self {
Destination::Xyz(xyz) => {
let ref_ = sc.register_xyz_destination(xyz.clone());
buffer.primitive(ref_);
Ok(())
}
Destination::Named(named) => named.serialize(sc, buffer),
}
}
}
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct NamedDestination {
pub(crate) name: Arc<String>,
pub(crate) xyz_dest: Arc<XyzDestination>,
}
impl From<NamedDestination> for Destination {
fn from(val: NamedDestination) -> Self {
Destination::Named(val)
}
}
impl NamedDestination {
pub fn new(name: String, xyz_dest: XyzDestination) -> Self {
Self {
name: Arc::new(name),
xyz_dest: Arc::new(xyz_dest),
}
}
pub(crate) fn serialize(
&self,
sc: &mut SerializeContext,
destination: Obj,
) -> KrillaResult<()> {
sc.register_named_destination(self.clone())
.ok_or_else(|| KrillaError::DuplicateNamedDestination(Arc::clone(&self.name)))?;
destination.primitive(Str(self.name.as_bytes()));
Ok(())
}
}
#[derive(Debug)]
struct XyzDestRepr {
page_index: usize,
point: Point,
}
impl Hash for XyzDestRepr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.page_index.hash(state);
self.point.x.to_bits().hash(state);
self.point.y.to_bits().hash(state);
}
}
impl PartialEq for XyzDestRepr {
fn eq(&self, other: &Self) -> bool {
self.page_index == other.page_index
&& self.point.x == other.point.x
&& self.point.y == other.point.y
}
}
impl Eq for XyzDestRepr {}
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub struct XyzDestination(Arc<XyzDestRepr>);
impl From<XyzDestination> for Destination {
fn from(val: XyzDestination) -> Self {
Destination::Xyz(val)
}
}
impl XyzDestination {
pub fn new(page_index: usize, point: Point) -> Self {
Self(Arc::new(XyzDestRepr { page_index, point }))
}
pub(crate) fn serialize(
&self,
sc: &mut SerializeContext,
chunk_container: &mut ChunkContainer,
root_ref: Ref,
) {
let chunk = &mut chunk_container.non_stream.destinations;
let destination = chunk.destination(root_ref);
let page_info = sc.page_infos().get(self.0.page_index).unwrap_or_else(|| {
panic!(
"attempted to link to page {}, but document only has {} pages",
self.0.page_index + 1,
sc.page_infos().len()
)
});
let (ref_, surface_size) = match page_info {
PageInfo::Krilla {
ref_, surface_size, ..
} => (ref_, surface_size),
PageInfo::Pdf { ref_, size, .. } => (ref_, size),
};
let page_ref = *ref_;
let page_size = surface_size.height();
let mut mapped_point = self.0.point.to_tsp();
let invert_transform = Transform::from_row(1.0, 0.0, 0.0, -1.0, 0.0, page_size);
invert_transform.map_point(&mut mapped_point);
destination
.page(page_ref)
.xyz(mapped_point.x, mapped_point.y, None);
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::annotation::{LinkAnnotation, Target};
use crate::error::KrillaError;
use crate::geom::{Point, Rect};
use crate::Document;
use super::{NamedDestination, XyzDestination};
#[test]
fn named_duplicate_rejected() {
let mut document = Document::new();
assert_eq!(
document.register_named_destination(NamedDestination::new(
"same".to_string(),
XyzDestination::new(0, Point::from_xy(0.0, 0.0)),
)),
Some(())
);
assert_eq!(
document.register_named_destination(NamedDestination::new(
"same".to_string(),
XyzDestination::new(0, Point::from_xy(100.0, 100.0)),
)),
None
);
}
#[test]
fn named_duplicate_same_location_allowed() {
let mut document = Document::new();
assert_eq!(
document.register_named_destination(NamedDestination::new(
"same".to_string(),
XyzDestination::new(0, Point::from_xy(0.0, 0.0)),
)),
Some(())
);
assert_eq!(
document.register_named_destination(NamedDestination::new(
"same".to_string(),
XyzDestination::new(0, Point::from_xy(0.0, 0.0)),
)),
Some(())
);
assert!(document.finish().is_ok());
}
#[test]
fn named_duplicate_annotation_rejected() {
let mut document = Document::new();
let mut page = document.start_page();
page.add_annotation(
LinkAnnotation::new(
Rect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(),
Target::Destination(
NamedDestination::new(
"same".to_string(),
XyzDestination::new(0, Point::from_xy(0.0, 0.0)),
)
.into(),
),
)
.into(),
);
page.add_annotation(
LinkAnnotation::new(
Rect::from_xywh(0.0, 100.0, 100.0, 100.0).unwrap(),
Target::Destination(
NamedDestination::new(
"same".to_string(),
XyzDestination::new(0, Point::from_xy(100.0, 100.0)),
)
.into(),
),
)
.into(),
);
drop(page);
assert_eq!(
document.finish(),
Err(KrillaError::DuplicateNamedDestination(Arc::new(
"same".to_string()
)))
);
}
#[test]
fn named_duplicate_manual_then_annotation_rejected() {
let mut document = Document::new();
assert_eq!(
document.register_named_destination(NamedDestination::new(
"same".to_string(),
XyzDestination::new(0, Point::from_xy(0.0, 0.0)),
)),
Some(())
);
let mut page = document.start_page();
page.add_annotation(
LinkAnnotation::new(
Rect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(),
Target::Destination(
NamedDestination::new(
"same".to_string(),
XyzDestination::new(0, Point::from_xy(100.0, 100.0)),
)
.into(),
),
)
.into(),
);
drop(page);
assert_eq!(
document.finish(),
Err(KrillaError::DuplicateNamedDestination(Arc::new(
"same".to_string()
)))
);
}
}