Skip to main content

pdf_annot/builder/
types.rs

1//! Value types used by the annotation builder: rectangles, subtypes, icons,
2//! and line endings.
3
4// Copyright (c) 2026 Innovation Trigger B.V.
5//
6// PDFluent is available under two licences, at your option: the GNU AGPLv3, or
7// the PDFluent Commercial Licence. See the LICENSE file in this repository --
8// that file travels with the copy you received, which a URL does not.
9
10#[cfg(feature = "write")]
11use lopdf::Object;
12
13/// PDF annotation rectangle in page user-space coordinates.
14///
15/// Coordinates are PDF user units (1/72 inch) with the origin at the
16/// bottom-left of the page. The rectangle is the annotation's clickable /
17/// visible area on the page; for markup annotations it is also used as
18/// the bounding box of the marked text region.
19///
20/// `(x0, y0)` is one corner and `(x1, y1)` is the opposite corner; the
21/// constructor does not require a particular ordering, but a zero-area
22/// rectangle (width == 0 or height == 0) is rejected by
23/// [`super::AnnotationBuilder::build`].
24#[cfg(feature = "write")]
25#[derive(Debug, Clone, Copy)]
26pub struct AnnotRect {
27    /// X coordinate of the first corner, in PDF user-space points.
28    pub x0: f64,
29    /// Y coordinate of the first corner, in PDF user-space points.
30    pub y0: f64,
31    /// X coordinate of the opposite corner, in PDF user-space points.
32    pub x1: f64,
33    /// Y coordinate of the opposite corner, in PDF user-space points.
34    pub y1: f64,
35}
36
37#[cfg(feature = "write")]
38impl AnnotRect {
39    /// Construct a rectangle from two opposite corners. No ordering is
40    /// required between `(x0, y0)` and `(x1, y1)` — width and height are
41    /// computed as absolute differences.
42    pub fn new(x0: f64, y0: f64, x1: f64, y1: f64) -> Self {
43        Self { x0, y0, x1, y1 }
44    }
45
46    /// Width in PDF user-space points (always non-negative).
47    pub fn width(&self) -> f64 {
48        (self.x1 - self.x0).abs()
49    }
50
51    /// Height in PDF user-space points (always non-negative).
52    pub fn height(&self) -> f64 {
53        (self.y1 - self.y0).abs()
54    }
55
56    pub(super) fn as_array(&self) -> Object {
57        Object::Array(vec![
58            Object::Real(self.x0 as f32),
59            Object::Real(self.y0 as f32),
60            Object::Real(self.x1 as f32),
61            Object::Real(self.y1 as f32),
62        ])
63    }
64}
65
66/// PDF annotation subtype as defined by ISO 32000-2 §12.5.6.
67///
68/// Each variant maps directly to the `/Subtype` name written into the
69/// annotation dictionary. Use the typed constructors on
70/// [`super::AnnotationBuilder`] (`square`, `highlight`, `link_uri`, …) instead
71/// of constructing this enum directly when possible — they wire up the
72/// per-subtype defaults that are required for a valid annotation.
73#[cfg(feature = "write")]
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum AnnotSubtype {
76    /// Filled or stroked rectangle (`/Square`). Used for emphasis or
77    /// region marking. Pair with [`super::AnnotationBuilder::interior_color`]
78    /// for a fill.
79    Square,
80    /// Filled or stroked ellipse (`/Circle`) inscribed in the
81    /// rectangle. Pair with [`super::AnnotationBuilder::interior_color`] for a
82    /// fill.
83    Circle,
84    /// Line segment (`/Line`) between two points, optionally with arrow
85    /// or other end caps. See [`super::AnnotationBuilder::line`] and
86    /// [`super::AnnotationBuilder::line_endings`].
87    Line,
88    /// Text-markup highlight (`/Highlight`) — semi-transparent yellow
89    /// fill over a text range. Use [`super::AnnotationBuilder::quad_points`]
90    /// to mark the actual text quadrilaterals.
91    Highlight,
92    /// Text-markup underline (`/Underline`) drawn beneath a text range.
93    Underline,
94    /// Text-markup strike-through (`/StrikeOut`) drawn through a text
95    /// range.
96    StrikeOut,
97    /// Text-markup squiggly underline (`/Squiggly`) — typically used to
98    /// flag spelling or grammar concerns.
99    Squiggly,
100    /// Free-text annotation (`/FreeText`) — text rendered directly on
101    /// the page (vs. a popup note). See
102    /// [`super::AnnotationBuilder::free_text`].
103    FreeText,
104    /// Sticky-note annotation (`/Text`). Despite the name, this is the
105    /// "popup comment" style — a small icon on the page that opens a
106    /// text bubble in the viewer. See
107    /// [`super::AnnotationBuilder::sticky_note`].
108    Text,
109    /// Rubber stamp annotation (`/Stamp`) — APPROVED, DRAFT,
110    /// CONFIDENTIAL, etc. See [`crate::StampName`] for the standard set and
111    /// [`super::AnnotationBuilder::stamp`] / [`super::AnnotationBuilder::stamp_custom`].
112    Stamp,
113    /// Free-hand ink annotation (`/Ink`) consisting of one or more
114    /// stroke paths. See [`super::AnnotationBuilder::ink`].
115    Ink,
116    /// Closed polygon (`/Polygon`) defined by a list of vertices.
117    /// Optionally filled with [`super::AnnotationBuilder::interior_color`].
118    Polygon,
119    /// Open polyline (`/PolyLine`) defined by a list of vertices.
120    /// Stroked but not filled.
121    PolyLine,
122    /// Hyperlink annotation (`/Link`) — invisible by default, the
123    /// viewer renders it as a clickable region. See
124    /// [`super::AnnotationBuilder::link_uri`] and
125    /// [`super::AnnotationBuilder::link_dest`].
126    Link,
127}
128
129#[cfg(feature = "write")]
130impl AnnotSubtype {
131    /// Parse a `/Subtype` name. `None` for subtypes the builder cannot draw
132    /// (widget, caret, sound, movie, …).
133    pub(crate) fn from_name(name: &str) -> Option<Self> {
134        match name {
135            "Square" => Some(Self::Square),
136            "Circle" => Some(Self::Circle),
137            "Line" => Some(Self::Line),
138            "Highlight" => Some(Self::Highlight),
139            "Underline" => Some(Self::Underline),
140            "StrikeOut" => Some(Self::StrikeOut),
141            "Squiggly" => Some(Self::Squiggly),
142            "FreeText" => Some(Self::FreeText),
143            "Text" => Some(Self::Text),
144            "Stamp" => Some(Self::Stamp),
145            "Ink" => Some(Self::Ink),
146            "Polygon" => Some(Self::Polygon),
147            "PolyLine" => Some(Self::PolyLine),
148            "Link" => Some(Self::Link),
149            _ => None,
150        }
151    }
152
153    pub(super) fn as_str(&self) -> &'static str {
154        match self {
155            Self::Square => "Square",
156            Self::Circle => "Circle",
157            Self::Line => "Line",
158            Self::Highlight => "Highlight",
159            Self::Underline => "Underline",
160            Self::StrikeOut => "StrikeOut",
161            Self::Squiggly => "Squiggly",
162            Self::FreeText => "FreeText",
163            Self::Text => "Text",
164            Self::Stamp => "Stamp",
165            Self::Ink => "Ink",
166            Self::Polygon => "Polygon",
167            Self::PolyLine => "PolyLine",
168            Self::Link => "Link",
169        }
170    }
171}
172
173/// Standard icon names for `/Text` (sticky-note) annotations per
174/// ISO 32000-2 §12.5.6.4 Table 180.
175///
176/// The icon is the small graphic that the viewer renders on the page;
177/// clicking it opens the popup note containing the annotation's
178/// `/Contents` text.
179#[cfg(feature = "write")]
180#[derive(Debug, Clone, Copy)]
181pub enum TextIcon {
182    /// Speech-bubble icon — the most common "this is a comment" icon.
183    Comment,
184    /// Key icon — typically used to flag access-control or
185    /// authentication notes.
186    Key,
187    /// Generic note-paper icon.
188    Note,
189    /// Question-mark / help icon for clarification requests.
190    Help,
191    /// New-paragraph icon (¶ with a plus) for editorial markup.
192    NewParagraph,
193    /// Paragraph icon (¶) for editorial markup.
194    Paragraph,
195    /// Caret/insert icon for indicating insertion points in editorial
196    /// review.
197    Insert,
198}
199
200#[cfg(feature = "write")]
201impl TextIcon {
202    pub(super) fn as_str(&self) -> &'static str {
203        match self {
204            Self::Comment => "Comment",
205            Self::Key => "Key",
206            Self::Note => "Note",
207            Self::Help => "Help",
208            Self::NewParagraph => "NewParagraph",
209            Self::Paragraph => "Paragraph",
210            Self::Insert => "Insert",
211        }
212    }
213}
214
215/// Line-ending style for `/Line`, `/PolyLine`, and free-text callout
216/// annotations per ISO 32000-2 §12.5.6.7 Table 179.
217///
218/// Pass two values to [`super::AnnotationBuilder::line_endings`] — the first
219/// applies to the start point, the second to the end point.
220#[cfg(feature = "write")]
221#[derive(Debug, Clone, Copy)]
222pub enum LineEnding {
223    /// `/None` — no end cap; the line terminates flush.
224    None,
225    /// `/Square` — solid square end cap.
226    Square,
227    /// `/Circle` — solid circle end cap.
228    Circle,
229    /// `/Diamond` — diamond / rhombus end cap.
230    Diamond,
231    /// `/OpenArrow` — open V-shaped arrowhead pointing away from the line.
232    OpenArrow,
233    /// `/ClosedArrow` — filled triangular arrowhead pointing away from the line.
234    ClosedArrow,
235    /// `/Butt` — short perpendicular tick at the end of the line.
236    Butt,
237    /// `/ROpenArrow` — reversed open arrow (points back along the line).
238    ROpenArrow,
239    /// `/RClosedArrow` — reversed filled arrow (points back along the line).
240    RClosedArrow,
241    /// `/Slash` — short diagonal slash at the end of the line.
242    Slash,
243}
244
245#[cfg(feature = "write")]
246impl LineEnding {
247    pub(super) fn as_str(&self) -> &'static str {
248        match self {
249            Self::None => "None",
250            Self::Square => "Square",
251            Self::Circle => "Circle",
252            Self::Diamond => "Diamond",
253            Self::OpenArrow => "OpenArrow",
254            Self::ClosedArrow => "ClosedArrow",
255            Self::Butt => "Butt",
256            Self::ROpenArrow => "ROpenArrow",
257            Self::RClosedArrow => "RClosedArrow",
258            Self::Slash => "Slash",
259        }
260    }
261}