1use kurbo::{Point, Rect};
57use pdfrum_object::{Name, ObjRef};
58use peniko::Color;
59
60use crate::annot::{
61 AnnotBorder, AnnotGoToView, AnnotLinkAction, AnnotLinkHighlight, AnnotMeta, AnnotSpec,
62 AnnotWrite, LineEndingStyle, Quad,
63};
64
65macro_rules! spec_builder {
73 ($builder:ident, $doc:literal) => {
74 impl $builder {
75 #[doc = $doc]
76 #[must_use]
79 pub fn contents(mut self, contents: impl Into<String>) -> Self {
80 self.contents = Some(contents.into());
81 self
82 }
83
84 #[doc = $doc]
85 #[must_use]
88 pub fn author(self, author: impl Into<String>) -> AnnotWrite {
89 AnnotSpec::from(self).with_author(author)
90 }
91
92 #[doc = $doc]
93 #[must_use]
96 pub fn name(self, name: impl Into<String>) -> AnnotWrite {
97 AnnotSpec::from(self).with_name(name)
98 }
99
100 #[doc = $doc]
101 #[must_use]
104 pub fn modified(self, modified: impl Into<String>) -> AnnotWrite {
105 AnnotSpec::from(self).with_modified(modified)
106 }
107
108 #[doc = $doc]
109 #[must_use]
112 pub fn flags(self, flags: pdfrum_doc::AnnotFlags) -> AnnotWrite {
113 AnnotSpec::from(self).with_flags(flags)
114 }
115
116 #[doc = $doc]
117 #[must_use]
120 pub fn meta(self, meta: AnnotMeta) -> AnnotWrite {
121 AnnotSpec::from(self).with_meta(meta)
122 }
123 }
124
125 impl From<$builder> for AnnotWrite {
129 fn from(builder: $builder) -> Self {
130 AnnotSpec::from(builder).into()
131 }
132 }
133 };
134}
135
136#[derive(Debug, Clone, PartialEq)]
153pub struct MarkupSpec {
154 kind: MarkupKind,
155 rect: Rect,
156 color: Color,
157 quads: Vec<Quad>,
158 contents: Option<String>,
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163#[non_exhaustive]
164pub enum MarkupKind {
165 Highlight,
167 Underline,
169 StrikeOut,
171 Squiggly,
173}
174
175impl MarkupSpec {
176 #[must_use]
181 pub fn new(kind: MarkupKind, rect: Rect, color: Color) -> Self {
182 Self {
183 kind,
184 rect,
185 color,
186 quads: vec![Quad::from(rect)],
187 contents: None,
188 }
189 }
190
191 #[must_use]
197 pub fn quads(mut self, quads: impl IntoIterator<Item = Quad>) -> Self {
198 self.quads = quads.into_iter().collect();
199 self
200 }
201}
202
203spec_builder!(MarkupSpec, "A text-markup annotation.");
204
205impl From<MarkupSpec> for AnnotSpec {
206 fn from(b: MarkupSpec) -> Self {
207 let MarkupSpec {
208 kind,
209 rect,
210 color,
211 quads,
212 contents,
213 } = b;
214 match kind {
215 MarkupKind::Highlight => Self::Highlight {
216 rect,
217 color,
218 quads,
219 contents,
220 },
221 MarkupKind::Underline => Self::Underline {
222 rect,
223 color,
224 quads,
225 contents,
226 },
227 MarkupKind::StrikeOut => Self::StrikeOut {
228 rect,
229 color,
230 quads,
231 contents,
232 },
233 MarkupKind::Squiggly => Self::Squiggly {
234 rect,
235 color,
236 quads,
237 contents,
238 },
239 }
240 }
241}
242
243#[derive(Debug, Clone, PartialEq)]
256pub struct TextSpec {
257 rect: Rect,
258 color: Color,
259 contents: Option<String>,
260 icon: Option<Name>,
261 open: bool,
262}
263
264impl TextSpec {
265 #[must_use]
267 pub fn new(rect: Rect, color: Color) -> Self {
268 Self {
269 rect,
270 color,
271 contents: None,
272 icon: None,
273 open: false,
274 }
275 }
276
277 #[must_use]
279 pub fn icon(mut self, icon: impl Into<Name>) -> Self {
280 self.icon = Some(icon.into());
281 self
282 }
283
284 #[must_use]
286 pub fn open(mut self, open: bool) -> Self {
287 self.open = open;
288 self
289 }
290}
291
292spec_builder!(TextSpec, "A sticky-note annotation.");
293
294impl From<TextSpec> for AnnotSpec {
295 fn from(b: TextSpec) -> Self {
296 let TextSpec {
297 rect,
298 color,
299 contents,
300 icon,
301 open,
302 } = b;
303 Self::Text {
304 rect,
305 color,
306 contents,
307 icon: icon.unwrap_or_else(|| crate::names::COMMENT.clone()),
308 open,
309 }
310 }
311}
312
313#[derive(Debug, Clone, PartialEq)]
315pub struct SquareSpec {
316 rect: Rect,
317 color: Color,
318 contents: Option<String>,
319 border: Option<AnnotBorder>,
320 interior: Option<Color>,
321}
322
323impl SquareSpec {
324 #[must_use]
326 pub fn new(rect: Rect, color: Color) -> Self {
327 Self {
328 rect,
329 color,
330 contents: None,
331 border: None,
332 interior: None,
333 }
334 }
335
336 #[must_use]
338 pub fn border(mut self, border: AnnotBorder) -> Self {
339 self.border = Some(border);
340 self
341 }
342
343 #[must_use]
348 pub fn interior(mut self, color: Color) -> Self {
349 self.interior = Some(color);
350 self
351 }
352}
353
354spec_builder!(SquareSpec, "A square annotation.");
355
356impl From<SquareSpec> for AnnotSpec {
357 fn from(b: SquareSpec) -> Self {
358 let SquareSpec {
359 rect,
360 color,
361 contents,
362 border,
363 interior,
364 } = b;
365 Self::Square {
366 rect,
367 color,
368 contents,
369 border: border.unwrap_or_default(),
370 interior,
371 }
372 }
373}
374
375#[derive(Debug, Clone, PartialEq)]
377pub struct CircleSpec {
378 rect: Rect,
379 color: Color,
380 contents: Option<String>,
381 border: Option<AnnotBorder>,
382 interior: Option<Color>,
383}
384
385impl CircleSpec {
386 #[must_use]
388 pub fn new(rect: Rect, color: Color) -> Self {
389 Self {
390 rect,
391 color,
392 contents: None,
393 border: None,
394 interior: None,
395 }
396 }
397
398 #[must_use]
400 pub fn border(mut self, border: AnnotBorder) -> Self {
401 self.border = Some(border);
402 self
403 }
404
405 #[must_use]
410 pub fn interior(mut self, color: Color) -> Self {
411 self.interior = Some(color);
412 self
413 }
414}
415
416spec_builder!(CircleSpec, "A circle annotation.");
417
418impl From<CircleSpec> for AnnotSpec {
419 fn from(b: CircleSpec) -> Self {
420 let CircleSpec {
421 rect,
422 color,
423 contents,
424 border,
425 interior,
426 } = b;
427 Self::Circle {
428 rect,
429 color,
430 contents,
431 border: border.unwrap_or_default(),
432 interior,
433 }
434 }
435}
436
437#[derive(Debug, Clone, PartialEq)]
439pub struct InkSpec {
440 rect: Rect,
441 color: Color,
442 strokes: Vec<Vec<Point>>,
443 contents: Option<String>,
444 border: Option<AnnotBorder>,
445}
446
447impl InkSpec {
448 #[must_use]
450 pub fn new(rect: Rect, color: Color, strokes: Vec<Vec<Point>>) -> Self {
451 Self {
452 rect,
453 color,
454 strokes,
455 contents: None,
456 border: None,
457 }
458 }
459
460 #[must_use]
462 pub fn border(mut self, border: AnnotBorder) -> Self {
463 self.border = Some(border);
464 self
465 }
466}
467
468spec_builder!(InkSpec, "An ink annotation.");
469
470impl From<InkSpec> for AnnotSpec {
471 fn from(b: InkSpec) -> Self {
472 let InkSpec {
473 rect,
474 color,
475 strokes,
476 contents,
477 border,
478 } = b;
479 Self::Ink {
480 rect,
481 color,
482 strokes,
483 contents,
484 border: border.unwrap_or_default(),
485 }
486 }
487}
488
489#[derive(Debug, Clone, PartialEq)]
506pub struct LineSpec {
507 rect: Rect,
508 color: Color,
509 start: Point,
510 end: Point,
511 contents: Option<String>,
512 border: Option<AnnotBorder>,
513 endings: Option<(LineEndingStyle, LineEndingStyle)>,
514 interior: Option<Color>,
515}
516
517impl LineSpec {
518 #[must_use]
520 pub fn new(rect: Rect, color: Color, start: Point, end: Point) -> Self {
521 Self {
522 rect,
523 color,
524 start,
525 end,
526 contents: None,
527 border: None,
528 endings: None,
529 interior: None,
530 }
531 }
532
533 #[must_use]
535 pub fn border(mut self, border: AnnotBorder) -> Self {
536 self.border = Some(border);
537 self
538 }
539
540 #[must_use]
542 pub fn endings(mut self, start: LineEndingStyle, end: LineEndingStyle) -> Self {
543 self.endings = Some((start, end));
544 self
545 }
546
547 #[must_use]
549 pub fn interior(mut self, color: Color) -> Self {
550 self.interior = Some(color);
551 self
552 }
553}
554
555spec_builder!(LineSpec, "A line annotation.");
556
557impl From<LineSpec> for AnnotSpec {
558 fn from(b: LineSpec) -> Self {
559 let LineSpec {
560 rect,
561 color,
562 start,
563 end,
564 contents,
565 border,
566 endings,
567 interior,
568 } = b;
569 Self::Line {
570 rect,
571 color,
572 start,
573 end,
574 contents,
575 border: border.unwrap_or_default(),
576 line_endings: endings,
577 interior,
578 }
579 }
580}
581
582#[derive(Debug, Clone, PartialEq)]
596pub struct LinkSpec {
597 rect: Rect,
598 action: AnnotLinkAction,
599 contents: Option<String>,
600 color: Option<Color>,
601 border: Option<AnnotBorder>,
602 highlight: Option<AnnotLinkHighlight>,
603}
604
605impl LinkSpec {
606 #[must_use]
608 pub fn new(rect: Rect, action: AnnotLinkAction) -> Self {
609 Self {
610 rect,
611 action,
612 contents: None,
613 color: None,
614 border: None,
615 highlight: None,
616 }
617 }
618
619 #[must_use]
621 pub fn uri(rect: Rect, uri: impl Into<String>) -> Self {
622 Self::new(rect, AnnotLinkAction::Uri(uri.into()))
623 }
624
625 #[must_use]
627 pub fn goto(rect: Rect, page: ObjRef, view: AnnotGoToView) -> Self {
628 Self::new(rect, AnnotLinkAction::GoTo { page, view })
629 }
630
631 #[must_use]
633 pub fn named(rect: Rect, name: impl Into<String>, page: ObjRef, view: AnnotGoToView) -> Self {
634 Self::new(
635 rect,
636 AnnotLinkAction::Named {
637 name: name.into(),
638 page,
639 view,
640 },
641 )
642 }
643
644 #[must_use]
646 pub fn named_existing(rect: Rect, name: impl Into<String>) -> Self {
647 Self::new(rect, AnnotLinkAction::NamedExisting { name: name.into() })
648 }
649
650 #[must_use]
652 pub fn goto_r(rect: Rect, file: impl Into<String>, page: i64, view: AnnotGoToView) -> Self {
653 Self::new(
654 rect,
655 AnnotLinkAction::GoToR {
656 file: file.into(),
657 dest: crate::AnnotRemoteDest::Page { page, view },
658 new_window: None,
659 },
660 )
661 }
662
663 #[must_use]
665 pub fn goto_r_named(rect: Rect, file: impl Into<String>, name: impl Into<String>) -> Self {
666 Self::new(
667 rect,
668 AnnotLinkAction::GoToR {
669 file: file.into(),
670 dest: crate::AnnotRemoteDest::Named(name.into()),
671 new_window: None,
672 },
673 )
674 }
675
676 #[must_use]
678 pub fn launch(rect: Rect, file: impl Into<String>) -> Self {
679 Self::new(rect, AnnotLinkAction::Launch { file: file.into() })
680 }
681
682 #[must_use]
684 pub fn color(mut self, color: Color) -> Self {
685 self.color = Some(color);
686 self
687 }
688
689 #[must_use]
691 pub fn border(mut self, border: AnnotBorder) -> Self {
692 self.border = Some(border);
693 self
694 }
695
696 #[must_use]
698 pub fn highlight(mut self, highlight: AnnotLinkHighlight) -> Self {
699 self.highlight = Some(highlight);
700 self
701 }
702}
703
704spec_builder!(LinkSpec, "A link annotation.");
705
706impl From<LinkSpec> for AnnotSpec {
707 fn from(b: LinkSpec) -> Self {
708 let LinkSpec {
709 rect,
710 action,
711 contents,
712 color,
713 border,
714 highlight,
715 } = b;
716 Self::Link {
717 rect,
718 action,
719 contents,
720 color,
721 border: border.unwrap_or_else(|| AnnotBorder::solid(1.0)),
725 highlight: highlight.unwrap_or_default(),
726 }
727 }
728}
729
730#[derive(Debug, Clone, PartialEq)]
732pub struct CaretSpec {
733 rect: Rect,
734 color: Color,
735 contents: Option<String>,
736}
737
738impl CaretSpec {
739 #[must_use]
741 pub fn new(rect: Rect, color: Color) -> Self {
742 Self {
743 rect,
744 color,
745 contents: None,
746 }
747 }
748}
749
750spec_builder!(CaretSpec, "A caret annotation.");
751
752impl From<CaretSpec> for AnnotSpec {
753 fn from(b: CaretSpec) -> Self {
754 let CaretSpec {
755 rect,
756 color,
757 contents,
758 } = b;
759 Self::Caret {
760 rect,
761 color,
762 contents,
763 }
764 }
765}
766
767#[derive(Debug, Clone, PartialEq)]
776pub struct FreeTextSpec {
777 rect: Rect,
778 color: Color,
779 contents: String,
780 da: Option<String>,
781 align: Option<pdfrum_doc::vt::Alignment>,
782}
783
784impl FreeTextSpec {
785 #[must_use]
788 pub fn new(rect: Rect, color: Color, contents: impl Into<String>) -> Self {
789 Self {
790 rect,
791 color,
792 contents: contents.into(),
793 da: None,
794 align: None,
795 }
796 }
797
798 #[must_use]
803 pub fn da(mut self, da: impl Into<String>) -> Self {
804 self.da = Some(da.into());
805 self
806 }
807
808 #[must_use]
812 pub fn align(mut self, align: pdfrum_doc::vt::Alignment) -> Self {
813 self.align = Some(align);
814 self
815 }
816
817 #[must_use]
819 pub fn author(self, author: impl Into<String>) -> AnnotWrite {
820 AnnotSpec::from(self).with_author(author)
821 }
822
823 #[must_use]
825 pub fn name(self, name: impl Into<String>) -> AnnotWrite {
826 AnnotSpec::from(self).with_name(name)
827 }
828
829 #[must_use]
831 pub fn modified(self, modified: impl Into<String>) -> AnnotWrite {
832 AnnotSpec::from(self).with_modified(modified)
833 }
834
835 #[must_use]
837 pub fn flags(self, flags: pdfrum_doc::AnnotFlags) -> AnnotWrite {
838 AnnotSpec::from(self).with_flags(flags)
839 }
840
841 #[must_use]
843 pub fn meta(self, meta: AnnotMeta) -> AnnotWrite {
844 AnnotSpec::from(self).with_meta(meta)
845 }
846}
847
848impl From<FreeTextSpec> for AnnotSpec {
849 fn from(b: FreeTextSpec) -> Self {
850 let FreeTextSpec {
851 rect,
852 color,
853 contents,
854 da,
855 align,
856 } = b;
857 Self::FreeText {
858 rect,
859 color,
860 contents,
861 da: da.unwrap_or_else(|| crate::DEFAULT_DA.to_owned()),
862 align,
863 }
864 }
865}
866
867impl From<FreeTextSpec> for AnnotWrite {
868 fn from(builder: FreeTextSpec) -> Self {
869 AnnotSpec::from(builder).into()
870 }
871}