Skip to main content

ebook_rs/
webpub.rs

1use crate::book::Book;
2use serde::{Deserialize, Serialize};
3
4/// Readium Webpub Manifest (application/webpub+json).
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct WebpubManifest {
7    #[serde(rename = "@context")]
8    pub context: String,
9    pub metadata: WebpubMetadata,
10    pub links: Vec<WebpubLink>,
11    #[serde(rename = "readingOrder")]
12    pub reading_order: Vec<WebpubLink>,
13    pub resources: Vec<WebpubLink>,
14    pub toc: Vec<WebpubLink>,
15}
16
17/// Readium Webpub Metadata.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct WebpubMetadata {
20    #[serde(rename = "@type")]
21    pub type_: String,
22    pub title: String,
23    #[serde(skip_serializing_if = "Vec::is_empty")]
24    pub author: Vec<String>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub publisher: Option<String>,
27    #[serde(skip_serializing_if = "Vec::is_empty")]
28    pub language: Vec<String>,
29    #[serde(rename = "readingProgression")]
30    pub reading_progression: String,
31}
32
33/// Readium Webpub Link item.
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct WebpubLink {
36    pub href: String,
37    #[serde(rename = "type")]
38    pub type_: String,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub title: Option<String>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub rel: Option<String>,
43    #[serde(skip_serializing_if = "Vec::is_empty", default)]
44    pub children: Vec<WebpubLink>,
45}
46
47impl Book {
48    /// Export the book as a Readium Webpub JSON Manifest (application/webpub+json).
49    pub fn to_webpub_manifest(&self) -> WebpubManifest {
50        let reading_progression = match self.metadata().direction {
51            crate::metadata::PageProgressionDirection::Rtl => "rtl",
52            _ => "ltr",
53        };
54
55        let metadata = WebpubMetadata {
56            type_: "http://schema.org/Book".to_string(),
57            title: self.metadata().title.clone(),
58            author: self.metadata().creators.clone(),
59            publisher: self.metadata().publishers.first().cloned(),
60            language: self.metadata().languages.clone(),
61            reading_progression: reading_progression.to_string(),
62        };
63
64        let links = vec![WebpubLink {
65            href: "manifest.json".to_string(),
66            type_: "application/webpub+json".to_string(),
67            title: None,
68            rel: Some("self".to_string()),
69            children: Vec::new(),
70        }];
71
72        let reading_order = self
73            .spine()
74            .iter()
75            .enumerate()
76            .map(|(idx, item)| WebpubLink {
77                href: item.href.clone(),
78                type_: if item.media_type.is_empty() {
79                    "application/xhtml+xml".to_string()
80                } else {
81                    item.media_type.clone()
82                },
83                title: Some(format!("Section {}", idx + 1)),
84                rel: None,
85                children: Vec::new(),
86            })
87            .collect();
88
89        let toc = self
90            .toc()
91            .iter()
92            .map(|p| WebpubLink {
93                href: p.href.clone(),
94                type_: "application/xhtml+xml".to_string(),
95                title: Some(p.label.clone()),
96                rel: None,
97                children: p
98                    .subitems
99                    .iter()
100                    .map(|sub| WebpubLink {
101                        href: sub.href.clone(),
102                        type_: "application/xhtml+xml".to_string(),
103                        title: Some(sub.label.clone()),
104                        rel: None,
105                        children: Vec::new(),
106                    })
107                    .collect(),
108            })
109            .collect();
110
111        WebpubManifest {
112            context: "https://readium.org/webpub-manifest/context.jsonld".to_string(),
113            metadata,
114            links,
115            reading_order,
116            resources: Vec::new(),
117            toc,
118        }
119    }
120
121    /// Export the Readium Webpub Manifest as a JSON string.
122    pub fn to_webpub_json(&self) -> Result<String, String> {
123        serde_json::to_string_pretty(&self.to_webpub_manifest())
124            .map_err(|e| format!("Failed to serialize Webpub manifest: {}", e))
125    }
126}