use core::f32;
use pdf_writer::types::AnnotationFlags;
use pdf_writer::{Chunk, Finish, Name, Ref, TextStr};
use crate::color::Color;
use crate::configure::{PdfVersion, ValidationError};
use crate::error::KrillaResult;
use crate::geom::{Quadrilateral, Rect};
use crate::interactive::action::Action;
use crate::interactive::destination::Destination;
use crate::page::page_root_transform;
use crate::serialize::SerializeContext;
use crate::surface::Location;
pub struct Annotation {
pub(crate) annotation_type: AnnotationType,
pub(crate) alt: Option<String>,
pub(crate) struct_parent: Option<i32>,
pub(crate) location: Option<Location>,
}
impl Annotation {
pub fn new_link(annotation: LinkAnnotation, alt_text: Option<String>) -> Self {
Self {
annotation_type: AnnotationType::Link(annotation),
alt: alt_text,
struct_parent: None,
location: None,
}
}
pub fn with_location(mut self, location: Option<Location>) -> Self {
self.location = location;
self
}
}
impl From<LinkAnnotation> for Annotation {
fn from(value: LinkAnnotation) -> Self {
Self {
annotation_type: AnnotationType::Link(value),
alt: None,
struct_parent: None,
location: None,
}
}
}
impl Annotation {
pub(crate) fn serialize(
&self,
sc: &mut SerializeContext,
root_ref: Ref,
page_height: f32,
) -> KrillaResult<Chunk> {
let mut chunk = Chunk::new();
let mut annotation = chunk
.indirect(root_ref)
.start::<pdf_writer::writers::Annotation>();
self.annotation_type
.serialize_type(sc, &mut annotation, page_height)?;
let AnnotationType::Link(l) = &self.annotation_type;
if l.border.is_none()
|| sc
.serialize_settings()
.configuration
.validator()
.requires_annotation_flags()
{
annotation.flags(AnnotationFlags::PRINT);
}
if let Some(struct_parent) = self.struct_parent {
annotation.struct_parent(struct_parent);
}
if let Some(alt_text) = &self.alt {
annotation.contents(TextStr(alt_text));
}
if self.alt.as_ref().is_none_or(String::is_empty) {
sc.register_validation_error(ValidationError::MissingAnnotationAltText(self.location));
}
annotation.finish();
Ok(chunk)
}
}
pub enum AnnotationType {
Link(LinkAnnotation),
}
impl AnnotationType {
fn serialize_type(
&self,
sc: &mut SerializeContext,
annotation: &mut pdf_writer::writers::Annotation,
page_height: f32,
) -> KrillaResult<()> {
match self {
AnnotationType::Link(l) => l.serialize_type(sc, annotation, page_height),
}
}
}
pub enum Target {
Destination(Destination),
Action(Action),
}
pub struct LinkBorder {
pub(crate) width: f32,
pub(crate) color: Color,
}
impl LinkBorder {
pub fn new(width: f32, color: Color) -> Self {
Self { width, color }
}
}
pub struct LinkAnnotation {
pub(crate) rect: Rect,
pub(crate) quad_points: Option<Vec<Quadrilateral>>,
pub(crate) target: Target,
pub(crate) border: Option<LinkBorder>,
}
impl LinkAnnotation {
pub fn new(rect: Rect, target: Target) -> Self {
Self {
rect,
quad_points: None,
target,
border: None,
}
}
pub fn new_with_quad_points(quad_points: Vec<Quadrilateral>, target: Target) -> Self {
assert!(!quad_points.is_empty());
let mut min_x = f32::INFINITY;
let mut min_y = f32::INFINITY;
let mut max_x = f32::NEG_INFINITY;
let mut max_y = f32::NEG_INFINITY;
for point in quad_points.iter().flat_map(|q| q.0) {
min_x = min_x.min(point.x);
min_y = min_y.min(point.y);
max_x = max_x.max(point.x);
max_y = max_y.max(point.y);
}
const EPSILON: f32 = 0.001;
let rect = Rect::from_ltrb(
min_x - EPSILON,
min_y - EPSILON,
max_x + EPSILON,
max_y + EPSILON,
)
.unwrap();
Self {
rect,
quad_points: Some(quad_points),
target,
border: None,
}
}
pub fn with_border(self, border: LinkBorder) -> Self {
Self {
border: Some(border),
..self
}
}
fn serialize_type(
&self,
sc: &mut SerializeContext,
annotation: &mut pdf_writer::writers::Annotation,
page_height: f32,
) -> KrillaResult<()> {
annotation.subtype(pdf_writer::types::AnnotationType::Link);
let actual_rect = self
.rect
.transform(page_root_transform(page_height))
.unwrap();
annotation.rect(actual_rect.to_pdf_rect());
annotation.border(
0.0,
0.0,
self.border.as_ref().map_or(0.0, |x| x.width),
None,
);
if let Some(border) = &self.border {
match border.color.to_regular() {
crate::color::RegularColor::Rgb(rgb) => {
let [r, g, b] = rgb.to_pdf_color();
annotation.color_rgb(r, g, b);
}
crate::color::RegularColor::Cmyk(cmyk) => {
let [c, m, y, k] = cmyk.to_pdf_color();
annotation.color_cmyk(c, m, y, k);
}
crate::color::RegularColor::Luma(gray) => {
annotation.color_gray(gray.to_pdf_color());
}
}
}
if sc.serialize_settings().pdf_version() >= PdfVersion::Pdf16 {
self.quad_points.as_ref().map(|p| {
annotation.quad_points(p.iter().flat_map(|q| q.0).flat_map(|p| {
let mut p = p.to_tsp();
page_root_transform(page_height).to_tsp().map_point(&mut p);
[p.x, p.y]
}))
});
}
match &self.target {
Target::Destination(destination) => {
destination.serialize(sc, annotation.insert(Name(b"Dest")))?
}
Target::Action(action) => action.serialize(sc, annotation.action())?,
};
Ok(())
}
}