Skip to main content

pdf_annot/
appearance.rs

1//! Appearance dictionary handling (ISO 32000-2 §12.5.5).
2
3// Copyright (c) 2026 Innovation Trigger B.V.
4//
5// PDFluent is available under two licences, at your option: the GNU AGPLv3, or
6// the PDFluent Commercial Licence. See the LICENSE file in this repository --
7// that file travels with the copy you received, which a URL does not.
8
9use pdf_syntax::object::dict::keys::*;
10use pdf_syntax::object::{Dict, Name, Stream};
11
12/// Wrapper for an annotation's `/AP` (appearance) dictionary.
13#[derive(Debug, Clone)]
14pub struct AppearanceDict<'a> {
15    dict: Dict<'a>,
16}
17
18impl<'a> AppearanceDict<'a> {
19    /// Try to extract the appearance dictionary from an annotation dict.
20    pub fn from_annot(annot_dict: &Dict<'a>) -> Option<Self> {
21        let dict = annot_dict.get::<Dict<'_>>(AP)?;
22        Some(Self { dict })
23    }
24
25    /// Return the normal appearance stream, resolving `/AS` if needed.
26    pub fn normal(&self, annot_dict: &Dict<'a>) -> Option<Stream<'a>> {
27        resolve_appearance(&self.dict, N, annot_dict)
28    }
29
30    /// Return the rollover appearance stream.
31    pub fn rollover(&self, annot_dict: &Dict<'a>) -> Option<Stream<'a>> {
32        resolve_appearance(&self.dict, R, annot_dict)
33    }
34
35    /// Return the down (mouse-pressed) appearance stream.
36    pub fn down(&self, annot_dict: &Dict<'a>) -> Option<Stream<'a>> {
37        resolve_appearance(&self.dict, D, annot_dict)
38    }
39}
40
41/// Resolve an appearance entry.
42fn resolve_appearance<'a>(
43    ap_dict: &Dict<'a>,
44    key: &[u8],
45    annot_dict: &Dict<'a>,
46) -> Option<Stream<'a>> {
47    if let Some(stream) = ap_dict.get::<Stream<'_>>(key) {
48        return Some(stream);
49    }
50    if let Some(sub_dict) = ap_dict.get::<Dict<'_>>(key) {
51        let appearance_state = annot_dict.get::<Name>(AS)?;
52        return sub_dict.get::<Stream<'_>>(appearance_state.as_ref());
53    }
54    None
55}