1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
use crate::{
assert_empty,
data_structures::Rectangle,
date::Date,
error::{ParseError, PdfResult},
objects::{Dictionary, Object, Reference},
pdf_enum,
resources::graphics_state_parameters::LineDashPattern,
Resolve,
};
use subtype::{AnnotationSubType, AnnotationSubTypeKind};
mod link;
mod state;
mod subtype;
mod text;
#[derive(Debug)]
pub struct Annotation {
base: BaseAnnotation,
sub_type: AnnotationSubType,
}
impl Annotation {
pub fn from_dict(mut dict: Dictionary, resolver: &mut impl Resolve) -> PdfResult<Self> {
let base = BaseAnnotation::from_dict(&mut dict, resolver)?;
let sub_type = AnnotationSubType::from_dict(&mut dict, &base, resolver)?;
assert_empty(dict);
Ok(Self { base, sub_type })
}
}
#[derive(Debug)]
pub(crate) struct BaseAnnotation {
subtype: AnnotationSubTypeKind,
/// The annotation rectangle, defining the location of the
/// annotation on the page in default user space units.
rect: Rectangle,
/// Text that shall be displayed for the annotation or, if this type
/// of annotation does not display text, an alternate description of the
/// annotation's contents in human-readable form. In either case, this
/// text is useful when extracting the document's contents in support
/// of accessibility to users with disabilities or for other purposes
contents: Option<String>,
/// An indirect reference to the page object with which this annotation
/// is associated.
///
/// This entry shall be present in screen annotations associated with
/// rendition actions
p: Option<Reference>,
/// The annotation name, a text string uniquely identifying it among all
/// the annotations on its page.
name: Option<String>,
/// The date and time when the annotation was most recently modified.
/// The format should be a date string, but conforming readers shall accept
/// and display a string in any format.
last_modified: Option<String>,
/// A set of flags specifying various characteristics of the annotation
///
/// Default value: 0
flags: AnnotationFlags,
/// An appearance dictionary specifying how the annotation shall be presented
/// visually on the page. Individual annotation handlers may ignore this entry
/// and provide their own appearances.
ap: Option<Appearance>,
/// The annotation's appearance state, which selects the applicable appearance
/// stream from an appearance subdictionary
appearance_stream_name: Option<String>,
/// An array specifying the characteristics of the annotation's border, which
/// shall be drawn as a rounded rectangle.
///
/// (PDF 1.0) The array consists of three numbers defining the horizontal corner
/// radius, vertical corner radius, and border width, all in default user space
/// units. If the corner radii are 0, the border has square (not rounded) corners;
/// if the border width is 0, no border is drawn.
///
/// (PDF 1.1) The array may have a fourth element, an optional dash array defining
/// a pattern of dashes and gaps that shall be used in drawing the border. The dash
/// array shall be specified in the same format as in the line dash pattern parameter
/// of the graphics state.
///
/// EXAMPLE
/// A Border value of [0 0 1 [3 2]] specifies a border 1 unit wide, with square
/// corners, drawn with 3-unit dashes alternating with 2-unit gaps.
///
/// (PDF 1.2) The dictionaries for some annotation types (such as free text and
/// polygon annotations) can include the BS entry. That entry specifies a border
/// style dictionary that has more settings than the array specified for the Border
/// entry. If an annotation dictionary includes the BS entry, then the Border entry
/// is ignored.
///
/// Default value: [0 0 1]
border: Border,
/// An array of numbers in the range 0.0 to 1.0, representing a colour used for the
/// following purposes:
/// - The background of the annotation's icon when closed
/// - The title bar of the annotation's pop-up window
/// - The border of a link annotation
///
/// The number of array elements determines the colour space in which the colour shall be defined:
/// - 0 No colour; transparent
/// - 1 DeviceGray
/// - 3 DeviceRGB
/// - 4 DeviceCMYK
c: Option<Vec<f32>>,
/// The integer key of the annotation's entry in the structural parent tree
struct_parent: Option<i32>,
/// An optional content group or optional content membership dictionary specifying the optional
/// content properties for the annotation. Before the annotation is drawn, its visibility
/// shall be determined based on this entry as well as the annotation flags specified in the
/// F entry. If it is determined to be invisible, the annotation shall be skipped, as if it were
/// not in the document.
oc: Option<OptionalContent>,
markup_dict: Option<MarkupAnnotation>,
}
#[derive(Debug)]
struct MarkupAnnotation {
/// The text label that shall be displayed in the title bar of the annotation's pop-up window
/// when open and active. This entry shall identify the user who added the annotation.
t: Option<String>,
/// An indirect reference to a pop-up annotation for entering or editing the text associated with
/// this annotation.
popup: Option<Reference>,
/// The constant opacity value that shall be used in painting the annotation.
///
/// This value shall apply to all visible elements of the annotation in its closed state (including
/// its background and border) but not to the pop-up window that appears when the annotation is opened.
///
/// The specified value shall not used if the annotation has an appearance stream; in that case, the
/// appearance stream shall specify any transparency. (However, if the compliant viewer regenerates
/// the annotation's appearance stream, it may incorporate the CA value into the stream's
/// content.) The implicit blend mode is Normal.
///
/// Default value: 1.0.
///
/// If no explicit appearance stream is defined for the annotation, it may be painted by
/// implementation-dependent means that do not necessarily conform to the PDF imaging model;
/// in this case, the effect of this entry is implementation-dependent as well.
ca: f32,
/// A rich text string that shall be displayed in the pop-up window when the annotation is opened.
rc: Option<RichTextString>,
/// The date and time when the annotation was created
creation_date: Option<Date>,
/// A reference to the annotation that this annotation is "in reply to."
///
/// Both annotations shall be on the same page of the document. The relationship between the two
/// annotations shall be specified by the RT entry. If this entry is present in an FDF file, its
/// type shall not be a dictionary but a text string containing the contents of the NM entry of the
/// annotation being replied to, to allow for a situation where the annotation being replied to
/// is not in the same FDF file.
irt: Option<Reference>,
/// Text representing a short description of the subject being addressed by the annotation.
subj: Option<String>,
/// A name specifying the relationship (the "reply type") between this annotation and one specified by IRT.
///
/// Valid values are:
/// * `R` The annotation shall be considered a reply to the annotation specified by IRT.
/// Conforming readers shall not display replies to an annotation individually but together
/// in the form of threaded comments.
/// * `Group` The annotation shall be grouped with the annotation specified by IRT
///
/// Default value: R.
rt: Option<ReplyType>,
/// A name describing the intent of the markup annotation. Intents allow conforming readers to distinguish
/// between different uses and behaviors of a single markup annotation type. If this entry is not present
/// or its value is the same as the annotation type, the annotation shall have no explicit intent and should
/// behave in a generic manner in a conforming reader.
///
/// Free text annotations, line annotations, polygon annotations, and polyline annotations have defined
/// intents, whose values are enumerated in the corresponding tables
// todo: should this be an enum
it: Option<String>,
/// An external data dictionary specifying data that shall be associated with the annotation
ex_data: Option<ExternalDataDictionary>,
}
// todo: this seems to only be used for 3d stuff
#[derive(Debug)]
struct ExternalDataDictionary {}
impl ExternalDataDictionary {
const TYPE: &'static str = "ExData";
pub fn from_dict(mut dict: Dictionary, resolver: &mut dyn Resolve) -> PdfResult<Self> {
dict.expect_type(Self::TYPE, resolver, false)?;
todo!()
}
}
pdf_enum!(
#[derive(Debug)]
enum ReplyType {
R = "R",
Group = "Group",
}
);
impl MarkupAnnotation {
pub fn from_dict(dict: &mut Dictionary, resolver: &mut dyn Resolve) -> PdfResult<Self> {
let t = dict.get_string("T", resolver)?;
let popup = dict.get_reference("Popup")?;
let ca = dict.get_number("CA", resolver)?.unwrap_or(1.0);
let rc = None;
let creation_date = dict.get_date("CreationDate", resolver)?;
let irt = dict.get_reference("IRT")?;
let subj = dict.get_string("Subj", resolver)?;
let rt = dict
.get_name("RT", resolver)?
.as_deref()
.map(ReplyType::from_str)
.transpose()?;
let it = dict.get_name("IT", resolver)?;
let ex_data = dict
.get_dict("ExData", resolver)?
.map(|d| ExternalDataDictionary::from_dict(d, resolver))
.transpose()?;
Ok(Self {
t,
popup,
ca,
rc,
creation_date,
irt,
subj,
rt,
it,
ex_data,
})
}
}
impl BaseAnnotation {
const TYPE: &'static str = "Annot";
pub fn from_dict(dict: &mut Dictionary, resolver: &mut dyn Resolve) -> PdfResult<Self> {
dict.expect_type(Self::TYPE, resolver, false)?;
let subtype = AnnotationSubTypeKind::from_str(&dict.expect_name("Subtype", resolver)?)?;
let rect = dict.expect_rectangle("Rect", resolver)?;
let contents = dict.get_string("Contents", resolver)?;
let p = dict.get_reference("P")?;
let name = dict.get_string("NM", resolver)?;
let last_modified = dict.get_string("M", resolver)?;
let flags = dict
.get_integer("F", resolver)?
.map(AnnotationFlags::from_integer)
.unwrap_or_default();
let ap = None;
let appearance_stream_name = dict.get_name("AS", resolver)?;
let border = dict
.get_arr("Border", resolver)?
.map(|border| Border::from_arr(border, resolver))
.transpose()?
.unwrap_or_default();
let c = dict
.get_arr("C", resolver)?
.map(|colors| {
colors
.into_iter()
.map(|color| resolver.assert_number(color))
.collect::<PdfResult<Vec<f32>>>()
})
.transpose()?;
let struct_parent = dict.get_integer("StructParent", resolver)?;
let oc = None;
let markup_dict = if subtype.is_markup() {
Some(MarkupAnnotation::from_dict(dict, resolver)?)
} else {
None
};
Ok(Self {
subtype,
rect,
contents,
p,
name,
last_modified,
flags,
ap,
appearance_stream_name,
border,
c,
struct_parent,
oc,
markup_dict,
})
}
}
#[derive(Debug)]
pub struct AnnotationFlags(u16);
impl AnnotationFlags {
const INVISIBLE: u16 = 1 << 0;
const HIDDEN: u16 = 1 << 1;
const PRINT: u16 = 1 << 2;
const NO_ZOOM: u16 = 1 << 3;
const NO_ROTATE: u16 = 1 << 4;
const NO_VIEW: u16 = 1 << 5;
const READONLY: u16 = 1 << 6;
const LOCKED: u16 = 1 << 7;
const TOGGLE_NO_VIEW: u16 = 1 << 8;
const LOCKED_CONTENTS: u16 = 1 << 9;
pub fn from_integer(i: i32) -> Self {
Self(i as u16)
}
/// If set, do not display the annotation if it does not belong to one of the
/// standard annotation types and no annotation handler is available.
///
/// If clear, display such an unknown annotation using an appearance stream specified
/// by its appearance dictionary, if any
pub fn is_invisible(&self) -> bool {
self.0 & Self::INVISIBLE != 0
}
/// If set, do not display or print the annotation or allow it to interact with
/// the user, regardless of its annotation type or whether an annotation handler
/// is available.
///
/// In cases where screen space is limited, the ability to hide and show annotations
/// selectively can be used in combination with appearance streams to display
/// auxiliary pop-up information similar in function to online help systems.
pub fn is_hidden(&self) -> bool {
self.0 & Self::HIDDEN != 0
}
/// If set, print the annotation when the page is printed. If clear, never print
/// the annotation, regardless of whether it is displayed on the screen.
///
/// This can be useful for annotations representing interactive pushbuttons, which
/// would serve no meaningful purpose on the printed page.
pub fn is_print(&self) -> bool {
self.0 & Self::PRINT != 0
}
/// If set, do not scale the annotation's appearance to match the magnification of
/// the page.
///
/// The location of the annotation on the page (defined by the upper-left corner of
/// its annotation rectangle) shall remain fixed, regardless of the page magnification.
pub fn is_no_zoom(&self) -> bool {
self.0 & Self::NO_ZOOM != 0
}
/// If set, do not rotate the annotation's appearance to match the rotation of the
/// page.
///
/// The upper-left corner of the annotation rectangle shall remain in a fixed location
/// on the page, regardless of the page rotation.
pub fn is_no_rotate(&self) -> bool {
self.0 & Self::NO_ROTATE != 0
}
/// If set, do not display the annotation on the screen or allow it to interact with
/// the user.
///
/// The annotation may be printed (depending on the setting of the Print flag) but
/// should be considered hidden for purposes of on-screen display and user interaction.
pub fn is_no_view(&self) -> bool {
self.0 & Self::NO_VIEW != 0
}
/// If set, do not allow the annotation to interact with the user.
///
/// The annotation may be displayed or printed (depending on the settings of the NoView
/// and Print flags) but should not respond to mouse clicks or change its appearance in
/// response to mouse motions. This flag shall be ignored for widget annotations; its
/// function is subsumed by the ReadOnly flag of the associated form field.
pub fn is_readonly(&self) -> bool {
self.0 & Self::READONLY != 0
}
/// If set, do not allow the annotation to be deleted or its properties (including
/// position and size) to be modified by the user. However, this flag does not restrict
/// changes to the annotation's contents, such as the value of a form field.
pub fn is_locked(&self) -> bool {
self.0 & Self::LOCKED != 0
}
/// If set, invert the interpretation of the NoView flag for certain events.
///
/// A typical use is to have an annotation that appears only when a mouse cursor is
/// held over it.
pub fn is_toggle_no_view(&self) -> bool {
self.0 & Self::TOGGLE_NO_VIEW != 0
}
/// If set, do not allow the contents of the annotation to be modified by the user.
///
/// This flag does not restrict deletion of the annotation or changes to other annotation
/// properties, such as position and size.
pub fn is_locked_contents(&self) -> bool {
self.0 & Self::LOCKED_CONTENTS != 0
}
}
impl Default for AnnotationFlags {
fn default() -> Self {
Self(0)
}
}
#[derive(Debug)]
struct Appearance;
#[derive(Debug)]
struct OptionalContent;
#[derive(Debug)]
struct RichTextString;
/// An annotation may optionally be surrounded by a border when displayed or printed. If present, the border
/// shall be drawn completely inside the annotation rectangle. In PDF 1.1, the characteristics of the border
/// shall be specified by the Border entry in the annotation dictionary. Beginning with PDF 1.2, the border
/// characteristics for some types of annotations may instead be specified in a border style dictionary designated
/// by the annotation's BS entry. Such dictionaries may also be used to specify the width and dash pattern
/// for the lines drawn by line, square, circle, and ink annotations. If neither the Border nor the BS entry
/// is present, the border shall be drawn as a solid line with a width of 1 point
#[derive(Debug)]
pub struct BorderStyle {
/// The border width in points. If this value is 0, no border shall drawn.
///
/// Default value: 1
w: u32,
/// The border style
s: Option<BorderStyleKind>,
/// A dash array defining a pattern of dashes and gaps that shall be used in drawing a dashed border (border
/// style D in the S entry). The dash array shall be specified in the same format as in the line dash pattern
/// parameter of the graphics state. The dash phase is not specified and shall be assumed to be 0.
///
/// EXAMPLE: A `D` entry of [3 2] specifies a border drawn with 3-point dashes alternating with 2-point gaps.
///
/// Default value: [3].
d: Option<LineDashPattern>,
}
impl BorderStyle {
const TYPE: &'static str = "Border";
pub fn from_dict(mut dict: Dictionary, resolver: &mut dyn Resolve) -> PdfResult<Self> {
dict.expect_type(Self::TYPE, resolver, false)?;
let w = dict.get_unsigned_integer("W", resolver)?.unwrap_or(1);
let s = dict.get_name("S", resolver)?.map(BorderStyleKind::from_str);
let d = dict
.get_object("D", resolver)?
.map(|arr| LineDashPattern::from_arr(vec![arr, Object::Integer(0)], resolver))
.transpose()?;
Ok(Self { w, s, d })
}
}
#[derive(Debug)]
enum BorderStyleKind {
/// A solid rectangle surrounding the annotation
Solid,
/// A dashed rectangle surrounding the annotation
///
/// The dash pattern may be specified by the D entry
Dashed,
/// A simulated embossed rectangle that appears to be raised above the surface of the page
Beveled,
/// A simulated engraved rectangle that appears to be recessedcbelow the surface of the page
Inset,
/// A single line along the bottom of the annotation rectangle
Underline,
Other(String),
}
impl BorderStyleKind {
pub fn from_str(s: String) -> Self {
match s.as_ref() {
"S" => Self::Solid,
"D" => Self::Dashed,
"B" => Self::Beveled,
"I" => Self::Inset,
"U" => Self::Underline,
_ => Self::Other(s),
}
}
}
#[derive(Debug)]
struct Border {
horizontal_corner_radius: u32,
vertical_corner_radius: u32,
border_width: u32,
dash_array: Option<LineDashPattern>,
}
impl Border {
pub fn from_arr(mut arr: Vec<Object>, resolver: &mut dyn Resolve) -> PdfResult<Self> {
if arr.len() < 3 {
return Err(ParseError::ArrayOfInvalidLength {
expected: 3,
found: arr,
});
}
let dash_array = if arr.len() == 4 {
let arr = resolver.assert_arr(arr.pop().unwrap())?;
Some(LineDashPattern::from_arr(arr, resolver)?)
} else {
None
};
let border_width = resolver.assert_unsigned_integer(arr.pop().unwrap())?;
let vertical_corner_radius = resolver.assert_unsigned_integer(arr.pop().unwrap())?;
let horizontal_corner_radius = resolver.assert_unsigned_integer(arr.pop().unwrap())?;
Ok(Self {
horizontal_corner_radius,
vertical_corner_radius,
border_width,
dash_array,
})
}
}
impl Default for Border {
fn default() -> Self {
Self {
horizontal_corner_radius: 0,
vertical_corner_radius: 0,
border_width: 1,
dash_array: None,
}
}
}