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)]
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 pub(super) fn as_str(&self) -> &'static str {
132 match self {
133 Self::Square => "Square",
134 Self::Circle => "Circle",
135 Self::Line => "Line",
136 Self::Highlight => "Highlight",
137 Self::Underline => "Underline",
138 Self::StrikeOut => "StrikeOut",
139 Self::Squiggly => "Squiggly",
140 Self::FreeText => "FreeText",
141 Self::Text => "Text",
142 Self::Stamp => "Stamp",
143 Self::Ink => "Ink",
144 Self::Polygon => "Polygon",
145 Self::PolyLine => "PolyLine",
146 Self::Link => "Link",
147 }
148 }
149}
150
151/// Standard icon names for `/Text` (sticky-note) annotations per
152/// ISO 32000-2 §12.5.6.4 Table 180.
153///
154/// The icon is the small graphic that the viewer renders on the page;
155/// clicking it opens the popup note containing the annotation's
156/// `/Contents` text.
157#[cfg(feature = "write")]
158#[derive(Debug, Clone, Copy)]
159pub enum TextIcon {
160 /// Speech-bubble icon — the most common "this is a comment" icon.
161 Comment,
162 /// Key icon — typically used to flag access-control or
163 /// authentication notes.
164 Key,
165 /// Generic note-paper icon.
166 Note,
167 /// Question-mark / help icon for clarification requests.
168 Help,
169 /// New-paragraph icon (¶ with a plus) for editorial markup.
170 NewParagraph,
171 /// Paragraph icon (¶) for editorial markup.
172 Paragraph,
173 /// Caret/insert icon for indicating insertion points in editorial
174 /// review.
175 Insert,
176}
177
178#[cfg(feature = "write")]
179impl TextIcon {
180 pub(super) fn as_str(&self) -> &'static str {
181 match self {
182 Self::Comment => "Comment",
183 Self::Key => "Key",
184 Self::Note => "Note",
185 Self::Help => "Help",
186 Self::NewParagraph => "NewParagraph",
187 Self::Paragraph => "Paragraph",
188 Self::Insert => "Insert",
189 }
190 }
191}
192
193/// Line-ending style for `/Line`, `/PolyLine`, and free-text callout
194/// annotations per ISO 32000-2 §12.5.6.7 Table 179.
195///
196/// Pass two values to [`super::AnnotationBuilder::line_endings`] — the first
197/// applies to the start point, the second to the end point.
198#[cfg(feature = "write")]
199#[derive(Debug, Clone, Copy)]
200pub enum LineEnding {
201 /// `/None` — no end cap; the line terminates flush.
202 None,
203 /// `/Square` — solid square end cap.
204 Square,
205 /// `/Circle` — solid circle end cap.
206 Circle,
207 /// `/Diamond` — diamond / rhombus end cap.
208 Diamond,
209 /// `/OpenArrow` — open V-shaped arrowhead pointing away from the line.
210 OpenArrow,
211 /// `/ClosedArrow` — filled triangular arrowhead pointing away from the line.
212 ClosedArrow,
213 /// `/Butt` — short perpendicular tick at the end of the line.
214 Butt,
215 /// `/ROpenArrow` — reversed open arrow (points back along the line).
216 ROpenArrow,
217 /// `/RClosedArrow` — reversed filled arrow (points back along the line).
218 RClosedArrow,
219 /// `/Slash` — short diagonal slash at the end of the line.
220 Slash,
221}
222
223#[cfg(feature = "write")]
224impl LineEnding {
225 pub(super) fn as_str(&self) -> &'static str {
226 match self {
227 Self::None => "None",
228 Self::Square => "Square",
229 Self::Circle => "Circle",
230 Self::Diamond => "Diamond",
231 Self::OpenArrow => "OpenArrow",
232 Self::ClosedArrow => "ClosedArrow",
233 Self::Butt => "Butt",
234 Self::ROpenArrow => "ROpenArrow",
235 Self::RClosedArrow => "RClosedArrow",
236 Self::Slash => "Slash",
237 }
238 }
239}