use kurbo::{Point, Rect};
use pdfrum_object::{Name, ObjRef};
use peniko::Color;
use crate::annot::{
AnnotBorder, AnnotGoToView, AnnotLinkAction, AnnotLinkHighlight, AnnotMeta, AnnotSpec,
AnnotWrite, LineEndingStyle, Quad,
};
macro_rules! spec_builder {
($builder:ident, $doc:literal) => {
impl $builder {
#[doc = $doc]
#[must_use]
pub fn contents(mut self, contents: impl Into<String>) -> Self {
self.contents = Some(contents.into());
self
}
#[doc = $doc]
#[must_use]
pub fn author(self, author: impl Into<String>) -> AnnotWrite {
AnnotSpec::from(self).with_author(author)
}
#[doc = $doc]
#[must_use]
pub fn name(self, name: impl Into<String>) -> AnnotWrite {
AnnotSpec::from(self).with_name(name)
}
#[doc = $doc]
#[must_use]
pub fn modified(self, modified: impl Into<String>) -> AnnotWrite {
AnnotSpec::from(self).with_modified(modified)
}
#[doc = $doc]
#[must_use]
pub fn flags(self, flags: pdfrum_doc::AnnotFlags) -> AnnotWrite {
AnnotSpec::from(self).with_flags(flags)
}
#[doc = $doc]
#[must_use]
pub fn meta(self, meta: AnnotMeta) -> AnnotWrite {
AnnotSpec::from(self).with_meta(meta)
}
}
impl From<$builder> for AnnotWrite {
fn from(builder: $builder) -> Self {
AnnotSpec::from(builder).into()
}
}
};
}
#[derive(Debug, Clone, PartialEq)]
pub struct MarkupSpec {
kind: MarkupKind,
rect: Rect,
color: Color,
quads: Vec<Quad>,
contents: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum MarkupKind {
Highlight,
Underline,
StrikeOut,
Squiggly,
}
impl MarkupSpec {
#[must_use]
pub fn new(kind: MarkupKind, rect: Rect, color: Color) -> Self {
Self {
kind,
rect,
color,
quads: vec![Quad::from(rect)],
contents: None,
}
}
#[must_use]
pub fn quads(mut self, quads: impl IntoIterator<Item = Quad>) -> Self {
self.quads = quads.into_iter().collect();
self
}
}
spec_builder!(MarkupSpec, "A text-markup annotation.");
impl From<MarkupSpec> for AnnotSpec {
fn from(b: MarkupSpec) -> Self {
let MarkupSpec {
kind,
rect,
color,
quads,
contents,
} = b;
match kind {
MarkupKind::Highlight => Self::Highlight {
rect,
color,
quads,
contents,
},
MarkupKind::Underline => Self::Underline {
rect,
color,
quads,
contents,
},
MarkupKind::StrikeOut => Self::StrikeOut {
rect,
color,
quads,
contents,
},
MarkupKind::Squiggly => Self::Squiggly {
rect,
color,
quads,
contents,
},
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TextSpec {
rect: Rect,
color: Color,
contents: Option<String>,
icon: Option<Name>,
open: bool,
}
impl TextSpec {
#[must_use]
pub fn new(rect: Rect, color: Color) -> Self {
Self {
rect,
color,
contents: None,
icon: None,
open: false,
}
}
#[must_use]
pub fn icon(mut self, icon: impl Into<Name>) -> Self {
self.icon = Some(icon.into());
self
}
#[must_use]
pub fn open(mut self, open: bool) -> Self {
self.open = open;
self
}
}
spec_builder!(TextSpec, "A sticky-note annotation.");
impl From<TextSpec> for AnnotSpec {
fn from(b: TextSpec) -> Self {
let TextSpec {
rect,
color,
contents,
icon,
open,
} = b;
Self::Text {
rect,
color,
contents,
icon: icon.unwrap_or_else(|| crate::names::COMMENT.clone()),
open,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SquareSpec {
rect: Rect,
color: Color,
contents: Option<String>,
border: Option<AnnotBorder>,
interior: Option<Color>,
}
impl SquareSpec {
#[must_use]
pub fn new(rect: Rect, color: Color) -> Self {
Self {
rect,
color,
contents: None,
border: None,
interior: None,
}
}
#[must_use]
pub fn border(mut self, border: AnnotBorder) -> Self {
self.border = Some(border);
self
}
#[must_use]
pub fn interior(mut self, color: Color) -> Self {
self.interior = Some(color);
self
}
}
spec_builder!(SquareSpec, "A square annotation.");
impl From<SquareSpec> for AnnotSpec {
fn from(b: SquareSpec) -> Self {
let SquareSpec {
rect,
color,
contents,
border,
interior,
} = b;
Self::Square {
rect,
color,
contents,
border: border.unwrap_or_default(),
interior,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CircleSpec {
rect: Rect,
color: Color,
contents: Option<String>,
border: Option<AnnotBorder>,
interior: Option<Color>,
}
impl CircleSpec {
#[must_use]
pub fn new(rect: Rect, color: Color) -> Self {
Self {
rect,
color,
contents: None,
border: None,
interior: None,
}
}
#[must_use]
pub fn border(mut self, border: AnnotBorder) -> Self {
self.border = Some(border);
self
}
#[must_use]
pub fn interior(mut self, color: Color) -> Self {
self.interior = Some(color);
self
}
}
spec_builder!(CircleSpec, "A circle annotation.");
impl From<CircleSpec> for AnnotSpec {
fn from(b: CircleSpec) -> Self {
let CircleSpec {
rect,
color,
contents,
border,
interior,
} = b;
Self::Circle {
rect,
color,
contents,
border: border.unwrap_or_default(),
interior,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct InkSpec {
rect: Rect,
color: Color,
strokes: Vec<Vec<Point>>,
contents: Option<String>,
border: Option<AnnotBorder>,
}
impl InkSpec {
#[must_use]
pub fn new(rect: Rect, color: Color, strokes: Vec<Vec<Point>>) -> Self {
Self {
rect,
color,
strokes,
contents: None,
border: None,
}
}
#[must_use]
pub fn border(mut self, border: AnnotBorder) -> Self {
self.border = Some(border);
self
}
}
spec_builder!(InkSpec, "An ink annotation.");
impl From<InkSpec> for AnnotSpec {
fn from(b: InkSpec) -> Self {
let InkSpec {
rect,
color,
strokes,
contents,
border,
} = b;
Self::Ink {
rect,
color,
strokes,
contents,
border: border.unwrap_or_default(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LineSpec {
rect: Rect,
color: Color,
start: Point,
end: Point,
contents: Option<String>,
border: Option<AnnotBorder>,
endings: Option<(LineEndingStyle, LineEndingStyle)>,
interior: Option<Color>,
}
impl LineSpec {
#[must_use]
pub fn new(rect: Rect, color: Color, start: Point, end: Point) -> Self {
Self {
rect,
color,
start,
end,
contents: None,
border: None,
endings: None,
interior: None,
}
}
#[must_use]
pub fn border(mut self, border: AnnotBorder) -> Self {
self.border = Some(border);
self
}
#[must_use]
pub fn endings(mut self, start: LineEndingStyle, end: LineEndingStyle) -> Self {
self.endings = Some((start, end));
self
}
#[must_use]
pub fn interior(mut self, color: Color) -> Self {
self.interior = Some(color);
self
}
}
spec_builder!(LineSpec, "A line annotation.");
impl From<LineSpec> for AnnotSpec {
fn from(b: LineSpec) -> Self {
let LineSpec {
rect,
color,
start,
end,
contents,
border,
endings,
interior,
} = b;
Self::Line {
rect,
color,
start,
end,
contents,
border: border.unwrap_or_default(),
line_endings: endings,
interior,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LinkSpec {
rect: Rect,
action: AnnotLinkAction,
contents: Option<String>,
color: Option<Color>,
border: Option<AnnotBorder>,
highlight: Option<AnnotLinkHighlight>,
}
impl LinkSpec {
#[must_use]
pub fn new(rect: Rect, action: AnnotLinkAction) -> Self {
Self {
rect,
action,
contents: None,
color: None,
border: None,
highlight: None,
}
}
#[must_use]
pub fn uri(rect: Rect, uri: impl Into<String>) -> Self {
Self::new(rect, AnnotLinkAction::Uri(uri.into()))
}
#[must_use]
pub fn goto(rect: Rect, page: ObjRef, view: AnnotGoToView) -> Self {
Self::new(rect, AnnotLinkAction::GoTo { page, view })
}
#[must_use]
pub fn named(rect: Rect, name: impl Into<String>, page: ObjRef, view: AnnotGoToView) -> Self {
Self::new(
rect,
AnnotLinkAction::Named {
name: name.into(),
page,
view,
},
)
}
#[must_use]
pub fn named_existing(rect: Rect, name: impl Into<String>) -> Self {
Self::new(rect, AnnotLinkAction::NamedExisting { name: name.into() })
}
#[must_use]
pub fn goto_r(rect: Rect, file: impl Into<String>, page: i64, view: AnnotGoToView) -> Self {
Self::new(
rect,
AnnotLinkAction::GoToR {
file: file.into(),
dest: crate::AnnotRemoteDest::Page { page, view },
new_window: None,
},
)
}
#[must_use]
pub fn goto_r_named(rect: Rect, file: impl Into<String>, name: impl Into<String>) -> Self {
Self::new(
rect,
AnnotLinkAction::GoToR {
file: file.into(),
dest: crate::AnnotRemoteDest::Named(name.into()),
new_window: None,
},
)
}
#[must_use]
pub fn launch(rect: Rect, file: impl Into<String>) -> Self {
Self::new(rect, AnnotLinkAction::Launch { file: file.into() })
}
#[must_use]
pub fn color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
#[must_use]
pub fn border(mut self, border: AnnotBorder) -> Self {
self.border = Some(border);
self
}
#[must_use]
pub fn highlight(mut self, highlight: AnnotLinkHighlight) -> Self {
self.highlight = Some(highlight);
self
}
}
spec_builder!(LinkSpec, "A link annotation.");
impl From<LinkSpec> for AnnotSpec {
fn from(b: LinkSpec) -> Self {
let LinkSpec {
rect,
action,
contents,
color,
border,
highlight,
} = b;
Self::Link {
rect,
action,
contents,
color,
border: border.unwrap_or_else(|| AnnotBorder::solid(1.0)),
highlight: highlight.unwrap_or_default(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CaretSpec {
rect: Rect,
color: Color,
contents: Option<String>,
}
impl CaretSpec {
#[must_use]
pub fn new(rect: Rect, color: Color) -> Self {
Self {
rect,
color,
contents: None,
}
}
}
spec_builder!(CaretSpec, "A caret annotation.");
impl From<CaretSpec> for AnnotSpec {
fn from(b: CaretSpec) -> Self {
let CaretSpec {
rect,
color,
contents,
} = b;
Self::Caret {
rect,
color,
contents,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FreeTextSpec {
rect: Rect,
color: Color,
contents: String,
da: Option<String>,
align: Option<pdfrum_doc::vt::Alignment>,
}
impl FreeTextSpec {
#[must_use]
pub fn new(rect: Rect, color: Color, contents: impl Into<String>) -> Self {
Self {
rect,
color,
contents: contents.into(),
da: None,
align: None,
}
}
#[must_use]
pub fn da(mut self, da: impl Into<String>) -> Self {
self.da = Some(da.into());
self
}
#[must_use]
pub fn align(mut self, align: pdfrum_doc::vt::Alignment) -> Self {
self.align = Some(align);
self
}
#[must_use]
pub fn author(self, author: impl Into<String>) -> AnnotWrite {
AnnotSpec::from(self).with_author(author)
}
#[must_use]
pub fn name(self, name: impl Into<String>) -> AnnotWrite {
AnnotSpec::from(self).with_name(name)
}
#[must_use]
pub fn modified(self, modified: impl Into<String>) -> AnnotWrite {
AnnotSpec::from(self).with_modified(modified)
}
#[must_use]
pub fn flags(self, flags: pdfrum_doc::AnnotFlags) -> AnnotWrite {
AnnotSpec::from(self).with_flags(flags)
}
#[must_use]
pub fn meta(self, meta: AnnotMeta) -> AnnotWrite {
AnnotSpec::from(self).with_meta(meta)
}
}
impl From<FreeTextSpec> for AnnotSpec {
fn from(b: FreeTextSpec) -> Self {
let FreeTextSpec {
rect,
color,
contents,
da,
align,
} = b;
Self::FreeText {
rect,
color,
contents,
da: da.unwrap_or_else(|| crate::DEFAULT_DA.to_owned()),
align,
}
}
}
impl From<FreeTextSpec> for AnnotWrite {
fn from(builder: FreeTextSpec) -> Self {
AnnotSpec::from(builder).into()
}
}