Skip to main content

ebook_rs/
metadata.rs

1use ahash::AHashMap;
2use serde::{Deserialize, Serialize};
3
4/// Page progression direction (LTR, RTL, Default).
5#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "lowercase")]
7pub enum PageProgressionDirection {
8    #[default]
9    Ltr,
10    Rtl,
11    Default,
12}
13
14/// Metadata extracted from the EPUB package document (.opf).
15#[derive(Debug, Clone, Default, Serialize, Deserialize)]
16pub struct Metadata {
17    pub title: String,
18    pub creators: Vec<String>,
19    pub publishers: Vec<String>,
20    pub languages: Vec<String>,
21    pub rights: Option<String>,
22    pub description: Option<String>,
23    pub identifier: Option<String>,
24    pub pub_date: Option<String>,
25    pub modified_date: Option<String>,
26    pub subjects: Vec<String>,
27    pub cover_id: Option<String>,
28    pub cover_href: Option<String>,
29    pub direction: PageProgressionDirection,
30    pub meta_properties: AHashMap<String, String>,
31    pub accessibility: AccessibilityMetadata,
32}
33
34impl Metadata {
35    /// Return first creator (author) or empty string.
36    pub fn creator(&self) -> &str {
37        self.creators.first().map(|s| s.as_str()).unwrap_or("")
38    }
39
40    /// Return first publisher if present.
41    pub fn publisher(&self) -> Option<&str> {
42        self.publishers.first().map(|s| s.as_str())
43    }
44
45    /// Return first language or empty string.
46    pub fn language(&self) -> &str {
47        self.languages.first().map(|s| s.as_str()).unwrap_or("")
48    }
49}
50
51/// EPUB 3 Accessibility (a11y) Metadata conforming to W3C EPUB Accessibility 1.1 & Schema.org.
52#[derive(Debug, Clone, Default, Serialize, Deserialize)]
53pub struct AccessibilityMetadata {
54    pub access_modes: Vec<String>,
55    pub access_modes_sufficient: Vec<Vec<String>>,
56    pub accessibility_features: Vec<String>,
57    pub accessibility_hazards: Vec<String>,
58    pub accessibility_summary: Option<String>,
59    pub certified_by: Option<String>,
60    pub certifier_credential: Option<String>,
61    pub certifier_report: Option<String>,
62    pub is_accessible: bool,
63}
64
65impl AccessibilityMetadata {
66    /// Returns true if the EPUB specifies alternative text for visual assets.
67    pub fn has_alternative_text(&self) -> bool {
68        self.accessibility_features
69            .iter()
70            .any(|f| f.contains("alternativeText"))
71    }
72
73    /// Returns true if structural navigation (TOC/headings) is present.
74    pub fn has_structural_navigation(&self) -> bool {
75        self.accessibility_features
76            .iter()
77            .any(|f| f.contains("structuralNavigation") || f.contains("tableOfContents"))
78    }
79
80    /// Returns true if the book is screen-reader friendly (has text + structural nav).
81    pub fn is_screen_reader_friendly(&self) -> bool {
82        self.access_modes.iter().any(|m| m == "textual") && self.has_structural_navigation()
83    }
84}
85
86/// A manifest item representing a resource inside the EPUB.
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct ManifestItem {
89    pub id: String,
90    pub href: String,
91    pub full_path: String,
92    pub media_type: String,
93    pub properties: Vec<String>,
94    pub fallback: Option<String>,
95    pub media_overlay: Option<String>,
96}
97
98/// A spine item representing a reader page section in order.
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct SpineItem {
101    pub idref: String,
102    pub linear: bool,
103    pub properties: Vec<String>,
104    pub index: usize,
105    pub href: String,
106    pub media_type: String,
107}
108
109/// Guide or Landmark reference entry.
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct GuideItem {
112    pub type_: String,
113    pub title: String,
114    pub href: String,
115    pub full_path: String,
116}