1use crate::book::Book;
2use serde::{Deserialize, Serialize};
3
4#[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#[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#[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 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 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}