1use std::path::PathBuf;
18
19#[cfg(feature = "builder")]
20use crate::{
21 error::{EpubBuilderError, EpubError},
22 utils::ELEMENT_IN_DC_NAMESPACE,
23};
24
25#[derive(Debug, PartialEq, Eq)]
29pub enum EpubVersion {
30 Version2_0,
31 Version3_0,
32}
33
34#[derive(Debug, Clone)]
59pub struct MetadataItem {
60 pub id: Option<String>,
65
66 pub property: String,
72
73 pub value: String,
75
76 pub lang: Option<String>,
78
79 pub refined: Vec<MetadataRefinement>,
87}
88
89#[cfg(feature = "builder")]
90impl MetadataItem {
91 pub fn new(property: &str, value: &str) -> Self {
99 Self {
100 id: None,
101 property: property.to_string(),
102 value: value.to_string(),
103 lang: None,
104 refined: vec![],
105 }
106 }
107
108 pub fn with_id(&mut self, id: &str) -> &mut Self {
115 self.id = Some(id.to_string());
116 self
117 }
118
119 pub fn with_lang(&mut self, lang: &str) -> &mut Self {
126 self.lang = Some(lang.to_string());
127 self
128 }
129
130 pub fn append_refinement(&mut self, refine: MetadataRefinement) -> &mut Self {
140 if self.id.is_some() {
141 self.refined.push(refine);
142 } else {
143 }
145
146 self
147 }
148
149 pub fn build(&self) -> Self {
153 Self { ..self.clone() }
154 }
155
156 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
158 let mut attributes = Vec::new();
159
160 if !ELEMENT_IN_DC_NAMESPACE.contains(&self.property.as_str()) {
161 attributes.push(("property", self.property.as_str()));
162 }
163
164 if let Some(id) = &self.id {
165 attributes.push(("id", id.as_str()));
166 };
167
168 if let Some(lang) = &self.lang {
169 attributes.push(("lang", lang.as_str()));
170 };
171
172 attributes
173 }
174}
175
176#[derive(Debug, Clone)]
200pub struct MetadataRefinement {
201 pub refines: String,
202
203 pub property: String,
208
209 pub value: String,
211
212 pub lang: Option<String>,
214
215 pub scheme: Option<String>,
220}
221
222#[cfg(feature = "builder")]
223impl MetadataRefinement {
224 pub fn new(refines: &str, property: &str, value: &str) -> Self {
233 Self {
234 refines: refines.to_string(),
235 property: property.to_string(),
236 value: value.to_string(),
237 lang: None,
238 scheme: None,
239 }
240 }
241
242 pub fn with_lang(&mut self, lang: &str) -> &mut Self {
249 self.lang = Some(lang.to_string());
250 self
251 }
252
253 pub fn with_scheme(&mut self, scheme: &str) -> &mut Self {
260 self.scheme = Some(scheme.to_string());
261 self
262 }
263
264 pub fn build(&self) -> Self {
268 Self { ..self.clone() }
269 }
270
271 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
273 let mut attributes = Vec::new();
274
275 attributes.push(("refines", self.refines.as_str()));
276 attributes.push(("property", self.property.as_str()));
277
278 if let Some(lang) = &self.lang {
279 attributes.push(("lang", lang.as_str()));
280 };
281
282 if let Some(scheme) = &self.scheme {
283 attributes.push(("scheme", scheme.as_str()));
284 };
285
286 attributes
287 }
288}
289
290#[derive(Debug)]
299pub struct MetadataLinkItem {
300 pub href: String,
302
303 pub rel: String,
305
306 pub hreflang: Option<String>,
308
309 pub id: Option<String>,
313
314 pub mime: Option<String>,
316
317 pub properties: Option<String>,
322
323 pub refines: Option<String>,
328}
329
330#[derive(Debug, Clone)]
361pub struct ManifestItem {
362 pub id: String,
364
365 pub path: PathBuf,
371
372 pub mime: String,
374
375 pub properties: Option<String>,
381
382 pub fallback: Option<String>,
393}
394
395#[cfg(feature = "builder")]
396impl ManifestItem {
397 pub fn new(id: &str, path: &str) -> Result<Self, EpubError> {
408 if path.starts_with("../") {
409 return Err(
410 EpubBuilderError::IllegalManifestPath { manifest_id: id.to_string() }.into(),
411 );
412 }
413
414 Ok(Self {
415 id: id.to_string(),
416 path: PathBuf::from(path),
417 mime: String::new(),
418 properties: None,
419 fallback: None,
420 })
421 }
422
423 pub(crate) fn set_mime(self, mime: &str) -> Self {
425 Self {
426 id: self.id,
427 path: self.path,
428 mime: mime.to_string(),
429 properties: self.properties,
430 fallback: self.fallback,
431 }
432 }
433
434 pub fn append_property(&mut self, property: &str) -> &mut Self {
441 let new_properties = if let Some(properties) = &self.properties {
442 format!("{} {}", properties, property)
443 } else {
444 property.to_string()
445 };
446
447 self.properties = Some(new_properties);
448 self
449 }
450
451 pub fn with_fallback(&mut self, fallback: &str) -> &mut Self {
458 self.fallback = Some(fallback.to_string());
459 self
460 }
461
462 pub fn build(&self) -> Self {
466 Self { ..self.clone() }
467 }
468
469 pub fn attributes(&self) -> Vec<(&str, &str)> {
471 let mut attributes = Vec::new();
472
473 attributes.push(("id", self.id.as_str()));
474 attributes.push(("href", self.path.to_str().unwrap()));
475 attributes.push(("media-type", self.mime.as_str()));
476
477 if let Some(properties) = &self.properties {
478 attributes.push(("properties", properties.as_str()));
479 }
480
481 if let Some(fallback) = &self.fallback {
482 attributes.push(("fallback", fallback.as_str()));
483 }
484
485 attributes
486 }
487}
488
489#[derive(Debug, Clone)]
516pub struct SpineItem {
517 pub idref: String,
523
524 pub id: Option<String>,
526
527 pub properties: Option<String>,
533
534 pub linear: bool,
544}
545
546#[cfg(feature = "builder")]
547impl SpineItem {
548 pub fn new(idref: &str) -> Self {
557 Self {
558 idref: idref.to_string(),
559 id: None,
560 properties: None,
561 linear: true,
562 }
563 }
564
565 pub fn with_id(&mut self, id: &str) -> &mut Self {
572 self.id = Some(id.to_string());
573 self
574 }
575
576 pub fn append_property(&mut self, property: &str) -> &mut Self {
583 let new_properties = if let Some(properties) = &self.properties {
584 format!("{} {}", properties, property)
585 } else {
586 property.to_string()
587 };
588
589 self.properties = Some(new_properties);
590 self
591 }
592
593 pub fn set_linear(&mut self, linear: bool) -> &mut Self {
600 self.linear = linear;
601 self
602 }
603
604 pub fn build(&self) -> Self {
608 Self { ..self.clone() }
609 }
610
611 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
613 let mut attributes = Vec::new();
614
615 attributes.push(("idref", self.idref.as_str()));
616 attributes.push(("linear", if self.linear { "yes" } else { "no" }));
617
618 if let Some(id) = &self.id {
619 attributes.push(("id", id.as_str()));
620 }
621
622 if let Some(properties) = &self.properties {
623 attributes.push(("properties", properties.as_str()));
624 }
625
626 attributes
627 }
628}
629
630#[derive(Debug, Clone)]
636pub struct EncryptionData {
637 pub method: String,
644
645 pub data: String,
650}
651
652#[derive(Debug, Eq, Clone)]
677pub struct NavPoint {
678 pub label: String,
682
683 pub content: Option<PathBuf>,
688
689 pub children: Vec<NavPoint>,
691
692 pub play_order: Option<usize>,
697}
698
699#[cfg(feature = "builder")]
700impl NavPoint {
701 pub fn new(label: &str) -> Self {
708 Self {
709 label: label.to_string(),
710 content: None,
711 children: vec![],
712 play_order: None,
713 }
714 }
715
716 pub fn with_content(&mut self, content: &str) -> &mut Self {
723 self.content = Some(PathBuf::from(content));
724 self
725 }
726
727 pub fn append_child(&mut self, child: NavPoint) -> &mut Self {
734 self.children.push(child);
735 self
736 }
737
738 pub fn set_children(&mut self, children: Vec<NavPoint>) -> &mut Self {
745 self.children = children;
746 self
747 }
748
749 pub fn build(&self) -> Self {
753 Self { ..self.clone() }
754 }
755}
756
757impl Ord for NavPoint {
758 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
759 self.play_order.cmp(&other.play_order)
760 }
761}
762
763impl PartialOrd for NavPoint {
764 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
765 Some(self.cmp(other))
766 }
767}
768
769impl PartialEq for NavPoint {
770 fn eq(&self, other: &Self) -> bool {
771 self.play_order == other.play_order
772 }
773}
774
775#[cfg(feature = "content_builder")]
780#[derive(Debug, Clone, Eq, PartialEq)]
781pub struct Footnote {
782 pub locate: usize,
784
785 pub content: String,
787}
788
789#[cfg(feature = "content_builder")]
790impl Ord for Footnote {
791 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
792 self.locate.cmp(&other.locate)
793 }
794}
795
796#[cfg(feature = "content_builder")]
797impl PartialOrd for Footnote {
798 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
799 Some(self.cmp(other))
800 }
801}
802
803#[cfg(feature = "content_builder")]
805#[derive(Debug)]
806pub enum BlockType {
807 Text,
811
812 Quote,
817
818 Title,
822
823 Image,
827
828 Audio,
832
833 Video,
837
838 MathML,
843}
844
845#[derive(Debug, Default)]
850pub struct StyleOptions {
851 pub text: TextStyle,
853
854 pub color_scheme: ColorScheme,
858
859 pub layout: PageLayout,
863}
864
865#[derive(Debug)]
870pub struct TextStyle {
871 pub font_size: f32,
876
877 pub line_height: f32,
883
884 pub font_family: String,
889
890 pub font_weight: String,
895
896 pub font_style: String,
901
902 pub letter_spacing: String,
907
908 pub text_indent: f32,
913}
914
915impl Default for TextStyle {
916 fn default() -> Self {
917 Self {
918 font_size: 1.0,
919 line_height: 1.6,
920 font_family: "-apple-system, Roboto, sans-serif".to_string(),
921 font_weight: "normal".to_string(),
922 font_style: "normal".to_string(),
923 letter_spacing: "normal".to_string(),
924 text_indent: 2.0,
925 }
926 }
927}
928
929#[derive(Debug)]
934pub struct ColorScheme {
935 pub background: String,
940
941 pub text: String,
946
947 pub link: String,
952}
953
954impl Default for ColorScheme {
955 fn default() -> Self {
956 Self {
957 background: "#FFFFFF".to_string(),
958 text: "#000000".to_string(),
959 link: "#6f6f6f".to_string(),
960 }
961 }
962}
963
964#[derive(Debug)]
969pub struct PageLayout {
970 pub margin: usize,
974
975 pub text_align: TextAlign,
979
980 pub paragraph_spacing: usize,
984}
985
986impl Default for PageLayout {
987 fn default() -> Self {
988 Self {
989 margin: 20,
990 text_align: Default::default(),
991 paragraph_spacing: 16,
992 }
993 }
994}
995
996#[derive(Debug, Default, PartialEq)]
1000pub enum TextAlign {
1001 #[default]
1005 Left,
1006
1007 Right,
1011
1012 Justify,
1017
1018 Center,
1022}
1023
1024impl std::fmt::Display for TextAlign {
1025 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1026 match self {
1027 TextAlign::Left => write!(f, "left"),
1028 TextAlign::Right => write!(f, "right"),
1029 TextAlign::Justify => write!(f, "justify"),
1030 TextAlign::Center => write!(f, "center"),
1031 }
1032 }
1033}
1034
1035#[cfg(test)]
1036mod tests {
1037 mod navpoint_tests {
1038 use std::path::PathBuf;
1039
1040 use crate::types::NavPoint;
1041
1042 #[test]
1044 fn test_navpoint_partial_eq() {
1045 let nav1 = NavPoint {
1046 label: "Chapter 1".to_string(),
1047 content: Some(PathBuf::from("chapter1.html")),
1048 children: vec![],
1049 play_order: Some(1),
1050 };
1051
1052 let nav2 = NavPoint {
1053 label: "Chapter 1".to_string(),
1054 content: Some(PathBuf::from("chapter2.html")),
1055 children: vec![],
1056 play_order: Some(1),
1057 };
1058
1059 let nav3 = NavPoint {
1060 label: "Chapter 2".to_string(),
1061 content: Some(PathBuf::from("chapter1.html")),
1062 children: vec![],
1063 play_order: Some(2),
1064 };
1065
1066 assert_eq!(nav1, nav2); assert_ne!(nav1, nav3); }
1069
1070 #[test]
1072 fn test_navpoint_ord() {
1073 let nav1 = NavPoint {
1074 label: "Chapter 1".to_string(),
1075 content: Some(PathBuf::from("chapter1.html")),
1076 children: vec![],
1077 play_order: Some(1),
1078 };
1079
1080 let nav2 = NavPoint {
1081 label: "Chapter 2".to_string(),
1082 content: Some(PathBuf::from("chapter2.html")),
1083 children: vec![],
1084 play_order: Some(2),
1085 };
1086
1087 let nav3 = NavPoint {
1088 label: "Chapter 3".to_string(),
1089 content: Some(PathBuf::from("chapter3.html")),
1090 children: vec![],
1091 play_order: Some(3),
1092 };
1093
1094 assert!(nav1 < nav2);
1096 assert!(nav2 > nav1);
1097 assert!(nav1 == nav1);
1098
1099 assert_eq!(nav1.partial_cmp(&nav2), Some(std::cmp::Ordering::Less));
1101 assert_eq!(nav2.partial_cmp(&nav1), Some(std::cmp::Ordering::Greater));
1102 assert_eq!(nav1.partial_cmp(&nav1), Some(std::cmp::Ordering::Equal));
1103
1104 let mut nav_points = vec![nav2.clone(), nav3.clone(), nav1.clone()];
1106 nav_points.sort();
1107 assert_eq!(nav_points, vec![nav1, nav2, nav3]);
1108 }
1109
1110 #[test]
1112 fn test_navpoint_ord_with_none_play_order() {
1113 let nav_with_order = NavPoint {
1114 label: "Chapter 1".to_string(),
1115 content: Some(PathBuf::from("chapter1.html")),
1116 children: vec![],
1117 play_order: Some(1),
1118 };
1119
1120 let nav_without_order = NavPoint {
1121 label: "Preface".to_string(),
1122 content: Some(PathBuf::from("preface.html")),
1123 children: vec![],
1124 play_order: None,
1125 };
1126
1127 assert!(nav_without_order < nav_with_order);
1128 assert!(nav_with_order > nav_without_order);
1129
1130 let nav_without_order2 = NavPoint {
1131 label: "Introduction".to_string(),
1132 content: Some(PathBuf::from("intro.html")),
1133 children: vec![],
1134 play_order: None,
1135 };
1136
1137 assert!(nav_without_order == nav_without_order2);
1138 }
1139
1140 #[test]
1142 fn test_navpoint_with_children() {
1143 let child1 = NavPoint {
1144 label: "Section 1.1".to_string(),
1145 content: Some(PathBuf::from("section1_1.html")),
1146 children: vec![],
1147 play_order: Some(1),
1148 };
1149
1150 let child2 = NavPoint {
1151 label: "Section 1.2".to_string(),
1152 content: Some(PathBuf::from("section1_2.html")),
1153 children: vec![],
1154 play_order: Some(2),
1155 };
1156
1157 let parent1 = NavPoint {
1158 label: "Chapter 1".to_string(),
1159 content: Some(PathBuf::from("chapter1.html")),
1160 children: vec![child1.clone(), child2.clone()],
1161 play_order: Some(1),
1162 };
1163
1164 let parent2 = NavPoint {
1165 label: "Chapter 1".to_string(),
1166 content: Some(PathBuf::from("chapter1.html")),
1167 children: vec![child1.clone(), child2.clone()],
1168 play_order: Some(1),
1169 };
1170
1171 assert!(parent1 == parent2);
1172
1173 let parent3 = NavPoint {
1174 label: "Chapter 2".to_string(),
1175 content: Some(PathBuf::from("chapter2.html")),
1176 children: vec![child1.clone(), child2.clone()],
1177 play_order: Some(2),
1178 };
1179
1180 assert!(parent1 != parent3);
1181 assert!(parent1 < parent3);
1182 }
1183
1184 #[test]
1186 fn test_navpoint_with_none_content() {
1187 let nav1 = NavPoint {
1188 label: "Chapter 1".to_string(),
1189 content: None,
1190 children: vec![],
1191 play_order: Some(1),
1192 };
1193
1194 let nav2 = NavPoint {
1195 label: "Chapter 1".to_string(),
1196 content: None,
1197 children: vec![],
1198 play_order: Some(1),
1199 };
1200
1201 assert!(nav1 == nav2);
1202 }
1203 }
1204
1205 #[cfg(feature = "builder")]
1206 mod builder_tests {
1207 mod metadata_item {
1208 use crate::types::{MetadataItem, MetadataRefinement};
1209
1210 #[test]
1211 fn test_metadata_item_new() {
1212 let metadata_item = MetadataItem::new("title", "EPUB Test Book");
1213
1214 assert_eq!(metadata_item.property, "title");
1215 assert_eq!(metadata_item.value, "EPUB Test Book");
1216 assert_eq!(metadata_item.id, None);
1217 assert_eq!(metadata_item.lang, None);
1218 assert_eq!(metadata_item.refined.len(), 0);
1219 }
1220
1221 #[test]
1222 fn test_metadata_item_with_id() {
1223 let mut metadata_item = MetadataItem::new("creator", "John Doe");
1224 metadata_item.with_id("creator-1");
1225
1226 assert_eq!(metadata_item.property, "creator");
1227 assert_eq!(metadata_item.value, "John Doe");
1228 assert_eq!(metadata_item.id, Some("creator-1".to_string()));
1229 assert_eq!(metadata_item.lang, None);
1230 assert_eq!(metadata_item.refined.len(), 0);
1231 }
1232
1233 #[test]
1234 fn test_metadata_item_with_lang() {
1235 let mut metadata_item = MetadataItem::new("title", "测试书籍");
1236 metadata_item.with_lang("zh-CN");
1237
1238 assert_eq!(metadata_item.property, "title");
1239 assert_eq!(metadata_item.value, "测试书籍");
1240 assert_eq!(metadata_item.id, None);
1241 assert_eq!(metadata_item.lang, Some("zh-CN".to_string()));
1242 assert_eq!(metadata_item.refined.len(), 0);
1243 }
1244
1245 #[test]
1246 fn test_metadata_item_append_refinement() {
1247 let mut metadata_item = MetadataItem::new("creator", "John Doe");
1248 metadata_item.with_id("creator-1"); let refinement = MetadataRefinement::new("creator-1", "role", "author");
1251 metadata_item.append_refinement(refinement);
1252
1253 assert_eq!(metadata_item.refined.len(), 1);
1254 assert_eq!(metadata_item.refined[0].refines, "creator-1");
1255 assert_eq!(metadata_item.refined[0].property, "role");
1256 assert_eq!(metadata_item.refined[0].value, "author");
1257 }
1258
1259 #[test]
1260 fn test_metadata_item_append_refinement_without_id() {
1261 let mut metadata_item = MetadataItem::new("title", "Test Book");
1262 let refinement = MetadataRefinement::new("title", "title-type", "main");
1265 metadata_item.append_refinement(refinement);
1266
1267 assert_eq!(metadata_item.refined.len(), 0);
1269 }
1270
1271 #[test]
1272 fn test_metadata_item_build() {
1273 let mut metadata_item = MetadataItem::new("identifier", "urn:isbn:1234567890");
1274 metadata_item.with_id("pub-id").with_lang("en");
1275
1276 let built = metadata_item.build();
1277
1278 assert_eq!(built.property, "identifier");
1279 assert_eq!(built.value, "urn:isbn:1234567890");
1280 assert_eq!(built.id, Some("pub-id".to_string()));
1281 assert_eq!(built.lang, Some("en".to_string()));
1282 assert_eq!(built.refined.len(), 0);
1283 }
1284
1285 #[test]
1286 fn test_metadata_item_builder_chaining() {
1287 let mut metadata_item = MetadataItem::new("title", "EPUB 3.3 Guide");
1288 metadata_item.with_id("title").with_lang("en");
1289
1290 let refinement = MetadataRefinement::new("title", "title-type", "main");
1291 metadata_item.append_refinement(refinement);
1292
1293 let built = metadata_item.build();
1294
1295 assert_eq!(built.property, "title");
1296 assert_eq!(built.value, "EPUB 3.3 Guide");
1297 assert_eq!(built.id, Some("title".to_string()));
1298 assert_eq!(built.lang, Some("en".to_string()));
1299 assert_eq!(built.refined.len(), 1);
1300 }
1301
1302 #[test]
1303 fn test_metadata_item_attributes_dc_namespace() {
1304 let mut metadata_item = MetadataItem::new("title", "Test Book");
1305 metadata_item.with_id("title-id");
1306
1307 let attributes = metadata_item.attributes();
1308
1309 assert!(!attributes.iter().any(|(k, _)| k == &"property"));
1311 assert!(
1312 attributes
1313 .iter()
1314 .any(|(k, v)| k == &"id" && v == &"title-id")
1315 );
1316 }
1317
1318 #[test]
1319 fn test_metadata_item_attributes_non_dc_namespace() {
1320 let mut metadata_item = MetadataItem::new("meta", "value");
1321 metadata_item.with_id("meta-id");
1322
1323 let attributes = metadata_item.attributes();
1324
1325 assert!(attributes.iter().any(|(k, _)| k == &"property"));
1327 assert!(
1328 attributes
1329 .iter()
1330 .any(|(k, v)| k == &"id" && v == &"meta-id")
1331 );
1332 }
1333
1334 #[test]
1335 fn test_metadata_item_attributes_with_lang() {
1336 let mut metadata_item = MetadataItem::new("title", "Test Book");
1337 metadata_item.with_id("title-id").with_lang("en");
1338
1339 let attributes = metadata_item.attributes();
1340
1341 assert!(
1342 attributes
1343 .iter()
1344 .any(|(k, v)| k == &"id" && v == &"title-id")
1345 );
1346 assert!(attributes.iter().any(|(k, v)| k == &"lang" && v == &"en"));
1347 }
1348 }
1349
1350 mod metadata_refinement {
1351 use crate::types::MetadataRefinement;
1352
1353 #[test]
1354 fn test_metadata_refinement_new() {
1355 let refinement = MetadataRefinement::new("title", "title-type", "main");
1356
1357 assert_eq!(refinement.refines, "title");
1358 assert_eq!(refinement.property, "title-type");
1359 assert_eq!(refinement.value, "main");
1360 assert_eq!(refinement.lang, None);
1361 assert_eq!(refinement.scheme, None);
1362 }
1363
1364 #[test]
1365 fn test_metadata_refinement_with_lang() {
1366 let mut refinement = MetadataRefinement::new("creator", "role", "author");
1367 refinement.with_lang("en");
1368
1369 assert_eq!(refinement.refines, "creator");
1370 assert_eq!(refinement.property, "role");
1371 assert_eq!(refinement.value, "author");
1372 assert_eq!(refinement.lang, Some("en".to_string()));
1373 assert_eq!(refinement.scheme, None);
1374 }
1375
1376 #[test]
1377 fn test_metadata_refinement_with_scheme() {
1378 let mut refinement = MetadataRefinement::new("creator", "role", "author");
1379 refinement.with_scheme("marc:relators");
1380
1381 assert_eq!(refinement.refines, "creator");
1382 assert_eq!(refinement.property, "role");
1383 assert_eq!(refinement.value, "author");
1384 assert_eq!(refinement.lang, None);
1385 assert_eq!(refinement.scheme, Some("marc:relators".to_string()));
1386 }
1387
1388 #[test]
1389 fn test_metadata_refinement_build() {
1390 let mut refinement = MetadataRefinement::new("title", "alternate-script", "テスト");
1391 refinement.with_lang("ja").with_scheme("iso-15924");
1392
1393 let built = refinement.build();
1394
1395 assert_eq!(built.refines, "title");
1396 assert_eq!(built.property, "alternate-script");
1397 assert_eq!(built.value, "テスト");
1398 assert_eq!(built.lang, Some("ja".to_string()));
1399 assert_eq!(built.scheme, Some("iso-15924".to_string()));
1400 }
1401
1402 #[test]
1403 fn test_metadata_refinement_builder_chaining() {
1404 let mut refinement = MetadataRefinement::new("creator", "file-as", "Doe, John");
1405 refinement.with_lang("en").with_scheme("dcterms");
1406
1407 let built = refinement.build();
1408
1409 assert_eq!(built.refines, "creator");
1410 assert_eq!(built.property, "file-as");
1411 assert_eq!(built.value, "Doe, John");
1412 assert_eq!(built.lang, Some("en".to_string()));
1413 assert_eq!(built.scheme, Some("dcterms".to_string()));
1414 }
1415
1416 #[test]
1417 fn test_metadata_refinement_attributes() {
1418 let mut refinement = MetadataRefinement::new("title", "title-type", "main");
1419 refinement.with_lang("en").with_scheme("onix:codelist5");
1420
1421 let attributes = refinement.attributes();
1422
1423 assert!(
1424 attributes
1425 .iter()
1426 .any(|(k, v)| k == &"refines" && v == &"title")
1427 );
1428 assert!(
1429 attributes
1430 .iter()
1431 .any(|(k, v)| k == &"property" && v == &"title-type")
1432 );
1433 assert!(attributes.iter().any(|(k, v)| k == &"lang" && v == &"en"));
1434 assert!(
1435 attributes
1436 .iter()
1437 .any(|(k, v)| k == &"scheme" && v == &"onix:codelist5")
1438 );
1439 }
1440
1441 #[test]
1442 fn test_metadata_refinement_attributes_optional_fields() {
1443 let refinement = MetadataRefinement::new("creator", "role", "author");
1444 let attributes = refinement.attributes();
1445
1446 assert!(
1447 attributes
1448 .iter()
1449 .any(|(k, v)| k == &"refines" && v == &"creator")
1450 );
1451 assert!(
1452 attributes
1453 .iter()
1454 .any(|(k, v)| k == &"property" && v == &"role")
1455 );
1456
1457 assert!(!attributes.iter().any(|(k, _)| k == &"lang"));
1459 assert!(!attributes.iter().any(|(k, _)| k == &"scheme"));
1460 }
1461 }
1462
1463 mod manifest_item {
1464 use std::path::PathBuf;
1465
1466 use crate::types::ManifestItem;
1467
1468 #[test]
1469 fn test_manifest_item_new() {
1470 let manifest_item = ManifestItem::new("cover", "images/cover.jpg");
1471 assert!(manifest_item.is_ok());
1472
1473 let manifest_item = manifest_item.unwrap();
1474 assert_eq!(manifest_item.id, "cover");
1475 assert_eq!(manifest_item.path, PathBuf::from("images/cover.jpg"));
1476 assert_eq!(manifest_item.mime, "");
1477 assert_eq!(manifest_item.properties, None);
1478 assert_eq!(manifest_item.fallback, None);
1479 }
1480
1481 #[test]
1482 fn test_manifest_item_append_property() {
1483 let manifest_item = ManifestItem::new("nav", "nav.xhtml");
1484 assert!(manifest_item.is_ok());
1485
1486 let mut manifest_item = manifest_item.unwrap();
1487 manifest_item.append_property("nav");
1488
1489 assert_eq!(manifest_item.id, "nav");
1490 assert_eq!(manifest_item.path, PathBuf::from("nav.xhtml"));
1491 assert_eq!(manifest_item.mime, "");
1492 assert_eq!(manifest_item.properties, Some("nav".to_string()));
1493 assert_eq!(manifest_item.fallback, None);
1494 }
1495
1496 #[test]
1497 fn test_manifest_item_append_multiple_properties() {
1498 let manifest_item = ManifestItem::new("content", "content.xhtml");
1499 assert!(manifest_item.is_ok());
1500
1501 let mut manifest_item = manifest_item.unwrap();
1502 manifest_item
1503 .append_property("nav")
1504 .append_property("scripted")
1505 .append_property("svg");
1506
1507 assert_eq!(
1508 manifest_item.properties,
1509 Some("nav scripted svg".to_string())
1510 );
1511 }
1512
1513 #[test]
1514 fn test_manifest_item_with_fallback() {
1515 let manifest_item = ManifestItem::new("image", "image.tiff");
1516 assert!(manifest_item.is_ok());
1517
1518 let mut manifest_item = manifest_item.unwrap();
1519 manifest_item.with_fallback("image-fallback");
1520
1521 assert_eq!(manifest_item.id, "image");
1522 assert_eq!(manifest_item.path, PathBuf::from("image.tiff"));
1523 assert_eq!(manifest_item.mime, "");
1524 assert_eq!(manifest_item.properties, None);
1525 assert_eq!(manifest_item.fallback, Some("image-fallback".to_string()));
1526 }
1527
1528 #[test]
1529 fn test_manifest_item_set_mime() {
1530 let manifest_item = ManifestItem::new("style", "style.css");
1531 assert!(manifest_item.is_ok());
1532
1533 let manifest_item = manifest_item.unwrap();
1534 let updated_item = manifest_item.set_mime("text/css");
1535
1536 assert_eq!(updated_item.id, "style");
1537 assert_eq!(updated_item.path, PathBuf::from("style.css"));
1538 assert_eq!(updated_item.mime, "text/css");
1539 assert_eq!(updated_item.properties, None);
1540 assert_eq!(updated_item.fallback, None);
1541 }
1542
1543 #[test]
1544 fn test_manifest_item_build() {
1545 let manifest_item = ManifestItem::new("cover", "images/cover.jpg");
1546 assert!(manifest_item.is_ok());
1547
1548 let mut manifest_item = manifest_item.unwrap();
1549 manifest_item
1550 .append_property("cover-image")
1551 .with_fallback("cover-fallback");
1552
1553 let built = manifest_item.build();
1554
1555 assert_eq!(built.id, "cover");
1556 assert_eq!(built.path, PathBuf::from("images/cover.jpg"));
1557 assert_eq!(built.mime, "");
1558 assert_eq!(built.properties, Some("cover-image".to_string()));
1559 assert_eq!(built.fallback, Some("cover-fallback".to_string()));
1560 }
1561
1562 #[test]
1563 fn test_manifest_item_builder_chaining() {
1564 let manifest_item = ManifestItem::new("content", "content.xhtml");
1565 assert!(manifest_item.is_ok());
1566
1567 let mut manifest_item = manifest_item.unwrap();
1568 manifest_item
1569 .append_property("scripted")
1570 .append_property("svg")
1571 .with_fallback("fallback-content");
1572
1573 let built = manifest_item.build();
1574
1575 assert_eq!(built.id, "content");
1576 assert_eq!(built.path, PathBuf::from("content.xhtml"));
1577 assert_eq!(built.mime, "");
1578 assert_eq!(built.properties, Some("scripted svg".to_string()));
1579 assert_eq!(built.fallback, Some("fallback-content".to_string()));
1580 }
1581
1582 #[test]
1583 fn test_manifest_item_attributes() {
1584 let manifest_item = ManifestItem::new("nav", "nav.xhtml");
1585 assert!(manifest_item.is_ok());
1586
1587 let mut manifest_item = manifest_item.unwrap();
1588 manifest_item
1589 .append_property("nav")
1590 .with_fallback("fallback-nav");
1591
1592 let manifest_item = manifest_item.set_mime("application/xhtml+xml");
1594 let attributes = manifest_item.attributes();
1595
1596 assert!(attributes.contains(&("id", "nav")));
1598 assert!(attributes.contains(&("href", "nav.xhtml")));
1599 assert!(attributes.contains(&("media-type", "application/xhtml+xml")));
1600 assert!(attributes.contains(&("properties", "nav")));
1601 assert!(attributes.contains(&("fallback", "fallback-nav")));
1602 }
1603
1604 #[test]
1605 fn test_manifest_item_attributes_optional_fields() {
1606 let manifest_item = ManifestItem::new("simple", "simple.xhtml");
1607 assert!(manifest_item.is_ok());
1608
1609 let manifest_item = manifest_item.unwrap();
1610 let manifest_item = manifest_item.set_mime("application/xhtml+xml");
1611 let attributes = manifest_item.attributes();
1612
1613 assert!(attributes.contains(&("id", "simple")));
1615 assert!(attributes.contains(&("href", "simple.xhtml")));
1616 assert!(attributes.contains(&("media-type", "application/xhtml+xml")));
1617
1618 assert!(!attributes.iter().any(|(k, _)| k == &"properties"));
1620 assert!(!attributes.iter().any(|(k, _)| k == &"fallback"));
1621 }
1622
1623 #[test]
1624 fn test_manifest_item_path_handling() {
1625 let manifest_item = ManifestItem::new("test", "../images/test.png");
1626 assert!(manifest_item.is_err());
1627
1628 let err = manifest_item.unwrap_err();
1629 assert_eq!(
1630 err.to_string(),
1631 "Epub builder error: A manifest with id 'test' should not use a relative path starting with '../'."
1632 );
1633 }
1634 }
1635
1636 mod spine_item {
1637 use crate::types::SpineItem;
1638
1639 #[test]
1640 fn test_spine_item_new() {
1641 let spine_item = SpineItem::new("content_001");
1642
1643 assert_eq!(spine_item.idref, "content_001");
1644 assert_eq!(spine_item.id, None);
1645 assert_eq!(spine_item.properties, None);
1646 assert_eq!(spine_item.linear, true);
1647 }
1648
1649 #[test]
1650 fn test_spine_item_with_id() {
1651 let mut spine_item = SpineItem::new("content_001");
1652 spine_item.with_id("spine1");
1653
1654 assert_eq!(spine_item.idref, "content_001");
1655 assert_eq!(spine_item.id, Some("spine1".to_string()));
1656 assert_eq!(spine_item.properties, None);
1657 assert_eq!(spine_item.linear, true);
1658 }
1659
1660 #[test]
1661 fn test_spine_item_append_property() {
1662 let mut spine_item = SpineItem::new("content_001");
1663 spine_item.append_property("page-spread-left");
1664
1665 assert_eq!(spine_item.idref, "content_001");
1666 assert_eq!(spine_item.id, None);
1667 assert_eq!(spine_item.properties, Some("page-spread-left".to_string()));
1668 assert_eq!(spine_item.linear, true);
1669 }
1670
1671 #[test]
1672 fn test_spine_item_append_multiple_properties() {
1673 let mut spine_item = SpineItem::new("content_001");
1674 spine_item
1675 .append_property("page-spread-left")
1676 .append_property("rendition:layout-pre-paginated");
1677
1678 assert_eq!(
1679 spine_item.properties,
1680 Some("page-spread-left rendition:layout-pre-paginated".to_string())
1681 );
1682 }
1683
1684 #[test]
1685 fn test_spine_item_set_linear() {
1686 let mut spine_item = SpineItem::new("content_001");
1687 spine_item.set_linear(false);
1688
1689 assert_eq!(spine_item.idref, "content_001");
1690 assert_eq!(spine_item.id, None);
1691 assert_eq!(spine_item.properties, None);
1692 assert_eq!(spine_item.linear, false);
1693 }
1694
1695 #[test]
1696 fn test_spine_item_build() {
1697 let mut spine_item = SpineItem::new("content_001");
1698 spine_item
1699 .with_id("spine1")
1700 .append_property("page-spread-left")
1701 .set_linear(false);
1702
1703 let built = spine_item.build();
1704
1705 assert_eq!(built.idref, "content_001");
1706 assert_eq!(built.id, Some("spine1".to_string()));
1707 assert_eq!(built.properties, Some("page-spread-left".to_string()));
1708 assert_eq!(built.linear, false);
1709 }
1710
1711 #[test]
1712 fn test_spine_item_builder_chaining() {
1713 let mut spine_item = SpineItem::new("content_001");
1714 spine_item
1715 .with_id("spine1")
1716 .append_property("page-spread-left")
1717 .set_linear(false);
1718
1719 let built = spine_item.build();
1720
1721 assert_eq!(built.idref, "content_001");
1722 assert_eq!(built.id, Some("spine1".to_string()));
1723 assert_eq!(built.properties, Some("page-spread-left".to_string()));
1724 assert_eq!(built.linear, false);
1725 }
1726
1727 #[test]
1728 fn test_spine_item_attributes() {
1729 let mut spine_item = SpineItem::new("content_001");
1730 spine_item
1731 .with_id("spine1")
1732 .append_property("page-spread-left")
1733 .set_linear(false);
1734
1735 let attributes = spine_item.attributes();
1736
1737 assert!(attributes.contains(&("idref", "content_001")));
1739 assert!(attributes.contains(&("id", "spine1")));
1740 assert!(attributes.contains(&("properties", "page-spread-left")));
1741 assert!(attributes.contains(&("linear", "no"))); }
1743
1744 #[test]
1745 fn test_spine_item_attributes_linear_yes() {
1746 let spine_item = SpineItem::new("content_001");
1747 let attributes = spine_item.attributes();
1748
1749 assert!(attributes.contains(&("linear", "yes")));
1751 }
1752
1753 #[test]
1754 fn test_spine_item_attributes_optional_fields() {
1755 let spine_item = SpineItem::new("content_001");
1756 let attributes = spine_item.attributes();
1757
1758 assert!(attributes.contains(&("idref", "content_001")));
1760 assert!(attributes.contains(&("linear", "yes")));
1761
1762 assert!(!attributes.iter().any(|(k, _)| k == &"id"));
1764 assert!(!attributes.iter().any(|(k, _)| k == &"properties"));
1765 }
1766 }
1767
1768 mod navpoint {
1769
1770 use std::path::PathBuf;
1771
1772 use crate::types::NavPoint;
1773
1774 #[test]
1775 fn test_navpoint_new() {
1776 let navpoint = NavPoint::new("Test Chapter");
1777
1778 assert_eq!(navpoint.label, "Test Chapter");
1779 assert_eq!(navpoint.content, None);
1780 assert_eq!(navpoint.children.len(), 0);
1781 }
1782
1783 #[test]
1784 fn test_navpoint_with_content() {
1785 let mut navpoint = NavPoint::new("Test Chapter");
1786 navpoint.with_content("chapter1.html");
1787
1788 assert_eq!(navpoint.label, "Test Chapter");
1789 assert_eq!(navpoint.content, Some(PathBuf::from("chapter1.html")));
1790 assert_eq!(navpoint.children.len(), 0);
1791 }
1792
1793 #[test]
1794 fn test_navpoint_append_child() {
1795 let mut parent = NavPoint::new("Parent Chapter");
1796
1797 let mut child1 = NavPoint::new("Child Section 1");
1798 child1.with_content("section1.html");
1799
1800 let mut child2 = NavPoint::new("Child Section 2");
1801 child2.with_content("section2.html");
1802
1803 parent.append_child(child1.build());
1804 parent.append_child(child2.build());
1805
1806 assert_eq!(parent.children.len(), 2);
1807 assert_eq!(parent.children[0].label, "Child Section 1");
1808 assert_eq!(parent.children[1].label, "Child Section 2");
1809 }
1810
1811 #[test]
1812 fn test_navpoint_set_children() {
1813 let mut navpoint = NavPoint::new("Main Chapter");
1814 let children = vec![NavPoint::new("Section 1"), NavPoint::new("Section 2")];
1815
1816 navpoint.set_children(children);
1817
1818 assert_eq!(navpoint.children.len(), 2);
1819 assert_eq!(navpoint.children[0].label, "Section 1");
1820 assert_eq!(navpoint.children[1].label, "Section 2");
1821 }
1822
1823 #[test]
1824 fn test_navpoint_build() {
1825 let mut navpoint = NavPoint::new("Complete Chapter");
1826 navpoint.with_content("complete.html");
1827
1828 let child = NavPoint::new("Sub Section");
1829 navpoint.append_child(child.build());
1830
1831 let built = navpoint.build();
1832
1833 assert_eq!(built.label, "Complete Chapter");
1834 assert_eq!(built.content, Some(PathBuf::from("complete.html")));
1835 assert_eq!(built.children.len(), 1);
1836 assert_eq!(built.children[0].label, "Sub Section");
1837 }
1838
1839 #[test]
1840 fn test_navpoint_builder_chaining() {
1841 let mut navpoint = NavPoint::new("Chained Chapter");
1842
1843 navpoint
1844 .with_content("chained.html")
1845 .append_child(NavPoint::new("Child 1").build())
1846 .append_child(NavPoint::new("Child 2").build());
1847
1848 let built = navpoint.build();
1849
1850 assert_eq!(built.label, "Chained Chapter");
1851 assert_eq!(built.content, Some(PathBuf::from("chained.html")));
1852 assert_eq!(built.children.len(), 2);
1853 }
1854
1855 #[test]
1856 fn test_navpoint_empty_children() {
1857 let navpoint = NavPoint::new("No Children Chapter");
1858 let built = navpoint.build();
1859
1860 assert_eq!(built.children.len(), 0);
1861 }
1862
1863 #[test]
1864 fn test_navpoint_complex_hierarchy() {
1865 let mut root = NavPoint::new("Book");
1866
1867 let mut chapter1 = NavPoint::new("Chapter 1");
1868 chapter1
1869 .with_content("chapter1.html")
1870 .append_child(
1871 NavPoint::new("Section 1.1")
1872 .with_content("sec1_1.html")
1873 .build(),
1874 )
1875 .append_child(
1876 NavPoint::new("Section 1.2")
1877 .with_content("sec1_2.html")
1878 .build(),
1879 );
1880
1881 let mut chapter2 = NavPoint::new("Chapter 2");
1882 chapter2.with_content("chapter2.html").append_child(
1883 NavPoint::new("Section 2.1")
1884 .with_content("sec2_1.html")
1885 .build(),
1886 );
1887
1888 root.append_child(chapter1.build())
1889 .append_child(chapter2.build());
1890
1891 let book = root.build();
1892
1893 assert_eq!(book.label, "Book");
1894 assert_eq!(book.children.len(), 2);
1895
1896 let ch1 = &book.children[0];
1897 assert_eq!(ch1.label, "Chapter 1");
1898 assert_eq!(ch1.children.len(), 2);
1899
1900 let ch2 = &book.children[1];
1901 assert_eq!(ch2.label, "Chapter 2");
1902 assert_eq!(ch2.children.len(), 1);
1903 }
1904 }
1905 }
1906
1907 #[cfg(feature = "content_builder")]
1908 mod footnote_tests {
1909 use crate::types::Footnote;
1910
1911 #[test]
1912 fn test_footnote_basic_creation() {
1913 let footnote = Footnote {
1914 locate: 100,
1915 content: "Sample footnote".to_string(),
1916 };
1917
1918 assert_eq!(footnote.locate, 100);
1919 assert_eq!(footnote.content, "Sample footnote");
1920 }
1921
1922 #[test]
1923 fn test_footnote_equality() {
1924 let footnote1 = Footnote {
1925 locate: 100,
1926 content: "First note".to_string(),
1927 };
1928
1929 let footnote2 = Footnote {
1930 locate: 100,
1931 content: "First note".to_string(),
1932 };
1933
1934 let footnote3 = Footnote {
1935 locate: 100,
1936 content: "Different note".to_string(),
1937 };
1938
1939 let footnote4 = Footnote {
1940 locate: 200,
1941 content: "First note".to_string(),
1942 };
1943
1944 assert_eq!(footnote1, footnote2);
1945 assert_ne!(footnote1, footnote3);
1946 assert_ne!(footnote1, footnote4);
1947 }
1948
1949 #[test]
1950 fn test_footnote_ordering() {
1951 let footnote1 = Footnote {
1952 locate: 100,
1953 content: "First".to_string(),
1954 };
1955
1956 let footnote2 = Footnote {
1957 locate: 200,
1958 content: "Second".to_string(),
1959 };
1960
1961 let footnote3 = Footnote {
1962 locate: 150,
1963 content: "Middle".to_string(),
1964 };
1965
1966 assert!(footnote1 < footnote2);
1967 assert!(footnote2 > footnote1);
1968 assert!(footnote1 < footnote3);
1969 assert!(footnote3 < footnote2);
1970 assert_eq!(footnote1.cmp(&footnote1), std::cmp::Ordering::Equal);
1971 }
1972
1973 #[test]
1974 fn test_footnote_sorting() {
1975 let mut footnotes = vec![
1976 Footnote {
1977 locate: 300,
1978 content: "Third note".to_string(),
1979 },
1980 Footnote {
1981 locate: 100,
1982 content: "First note".to_string(),
1983 },
1984 Footnote {
1985 locate: 200,
1986 content: "Second note".to_string(),
1987 },
1988 ];
1989
1990 footnotes.sort();
1991
1992 assert_eq!(footnotes[0].locate, 100);
1993 assert_eq!(footnotes[1].locate, 200);
1994 assert_eq!(footnotes[2].locate, 300);
1995
1996 assert_eq!(footnotes[0].content, "First note");
1997 assert_eq!(footnotes[1].content, "Second note");
1998 assert_eq!(footnotes[2].content, "Third note");
1999 }
2000 }
2001
2002 #[cfg(feature = "content_builder")]
2003 mod block_type_tests {
2004 use crate::types::BlockType;
2005
2006 #[test]
2007 fn test_block_type_variants() {
2008 let _ = BlockType::Text;
2009 let _ = BlockType::Quote;
2010 let _ = BlockType::Title;
2011 let _ = BlockType::Image;
2012 let _ = BlockType::Audio;
2013 let _ = BlockType::Video;
2014 let _ = BlockType::MathML;
2015 }
2016
2017 #[test]
2018 fn test_block_type_debug() {
2019 let text = format!("{:?}", BlockType::Text);
2020 assert_eq!(text, "Text");
2021
2022 let quote = format!("{:?}", BlockType::Quote);
2023 assert_eq!(quote, "Quote");
2024
2025 let image = format!("{:?}", BlockType::Image);
2026 assert_eq!(image, "Image");
2027 }
2028 }
2029
2030 mod style_options_tests {
2031 use crate::types::{ColorScheme, PageLayout, StyleOptions, TextAlign, TextStyle};
2032
2033 #[test]
2034 fn test_style_options_default() {
2035 let options = StyleOptions::default();
2036
2037 assert_eq!(options.text.font_size, 1.0);
2038 assert_eq!(options.text.line_height, 1.6);
2039 assert_eq!(
2040 options.text.font_family,
2041 "-apple-system, Roboto, sans-serif"
2042 );
2043 assert_eq!(options.text.font_weight, "normal");
2044 assert_eq!(options.text.font_style, "normal");
2045 assert_eq!(options.text.letter_spacing, "normal");
2046 assert_eq!(options.text.text_indent, 2.0);
2047
2048 assert_eq!(options.color_scheme.background, "#FFFFFF");
2049 assert_eq!(options.color_scheme.text, "#000000");
2050 assert_eq!(options.color_scheme.link, "#6f6f6f");
2051
2052 assert_eq!(options.layout.margin, 20);
2053 assert_eq!(options.layout.text_align, TextAlign::Left);
2054 assert_eq!(options.layout.paragraph_spacing, 16);
2055 }
2056
2057 #[test]
2058 fn test_style_options_custom_values() {
2059 let text = TextStyle {
2060 font_size: 1.5,
2061 line_height: 2.0,
2062 font_family: "Georgia, serif".to_string(),
2063 font_weight: "bold".to_string(),
2064 font_style: "italic".to_string(),
2065 letter_spacing: "0.1em".to_string(),
2066 text_indent: 3.0,
2067 };
2068
2069 let color_scheme = ColorScheme {
2070 background: "#F0F0F0".to_string(),
2071 text: "#333333".to_string(),
2072 link: "#0066CC".to_string(),
2073 };
2074
2075 let layout = PageLayout {
2076 margin: 30,
2077 text_align: TextAlign::Center,
2078 paragraph_spacing: 20,
2079 };
2080
2081 let options = StyleOptions { text, color_scheme, layout };
2082
2083 assert_eq!(options.text.font_size, 1.5);
2084 assert_eq!(options.text.font_weight, "bold");
2085 assert_eq!(options.color_scheme.background, "#F0F0F0");
2086 assert_eq!(options.layout.text_align, TextAlign::Center);
2087 }
2088
2089 #[test]
2090 fn test_text_style_default() {
2091 let style = TextStyle::default();
2092
2093 assert_eq!(style.font_size, 1.0);
2094 assert_eq!(style.line_height, 1.6);
2095 assert_eq!(style.font_family, "-apple-system, Roboto, sans-serif");
2096 assert_eq!(style.font_weight, "normal");
2097 assert_eq!(style.font_style, "normal");
2098 assert_eq!(style.letter_spacing, "normal");
2099 assert_eq!(style.text_indent, 2.0);
2100 }
2101
2102 #[test]
2103 fn test_text_style_custom_values() {
2104 let style = TextStyle {
2105 font_size: 2.0,
2106 line_height: 1.8,
2107 font_family: "Times New Roman".to_string(),
2108 font_weight: "bold".to_string(),
2109 font_style: "italic".to_string(),
2110 letter_spacing: "0.05em".to_string(),
2111 text_indent: 0.0,
2112 };
2113
2114 assert_eq!(style.font_size, 2.0);
2115 assert_eq!(style.line_height, 1.8);
2116 assert_eq!(style.font_family, "Times New Roman");
2117 assert_eq!(style.font_weight, "bold");
2118 assert_eq!(style.font_style, "italic");
2119 assert_eq!(style.letter_spacing, "0.05em");
2120 assert_eq!(style.text_indent, 0.0);
2121 }
2122
2123 #[test]
2124 fn test_text_style_debug() {
2125 let style = TextStyle::default();
2126 let debug_str = format!("{:?}", style);
2127 assert!(debug_str.contains("TextStyle"));
2128 assert!(debug_str.contains("font_size"));
2129 }
2130
2131 #[test]
2132 fn test_color_scheme_default() {
2133 let scheme = ColorScheme::default();
2134
2135 assert_eq!(scheme.background, "#FFFFFF");
2136 assert_eq!(scheme.text, "#000000");
2137 assert_eq!(scheme.link, "#6f6f6f");
2138 }
2139
2140 #[test]
2141 fn test_color_scheme_custom_values() {
2142 let scheme = ColorScheme {
2143 background: "#000000".to_string(),
2144 text: "#FFFFFF".to_string(),
2145 link: "#00FF00".to_string(),
2146 };
2147
2148 assert_eq!(scheme.background, "#000000");
2149 assert_eq!(scheme.text, "#FFFFFF");
2150 assert_eq!(scheme.link, "#00FF00");
2151 }
2152
2153 #[test]
2154 fn test_color_scheme_debug() {
2155 let scheme = ColorScheme::default();
2156 let debug_str = format!("{:?}", scheme);
2157 assert!(debug_str.contains("ColorScheme"));
2158 assert!(debug_str.contains("background"));
2159 }
2160
2161 #[test]
2162 fn test_page_layout_default() {
2163 let layout = PageLayout::default();
2164
2165 assert_eq!(layout.margin, 20);
2166 assert_eq!(layout.text_align, TextAlign::Left);
2167 assert_eq!(layout.paragraph_spacing, 16);
2168 }
2169
2170 #[test]
2171 fn test_page_layout_custom_values() {
2172 let layout = PageLayout {
2173 margin: 40,
2174 text_align: TextAlign::Justify,
2175 paragraph_spacing: 24,
2176 };
2177
2178 assert_eq!(layout.margin, 40);
2179 assert_eq!(layout.text_align, TextAlign::Justify);
2180 assert_eq!(layout.paragraph_spacing, 24);
2181 }
2182
2183 #[test]
2184 fn test_page_layout_debug() {
2185 let layout = PageLayout::default();
2186 let debug_str = format!("{:?}", layout);
2187 assert!(debug_str.contains("PageLayout"));
2188 assert!(debug_str.contains("margin"));
2189 }
2190
2191 #[test]
2192 fn test_text_align_default() {
2193 let align = TextAlign::default();
2194 assert_eq!(align, TextAlign::Left);
2195 }
2196
2197 #[test]
2198 fn test_text_align_display() {
2199 assert_eq!(TextAlign::Left.to_string(), "left");
2200 assert_eq!(TextAlign::Right.to_string(), "right");
2201 assert_eq!(TextAlign::Justify.to_string(), "justify");
2202 assert_eq!(TextAlign::Center.to_string(), "center");
2203 }
2204
2205 #[test]
2206 fn test_text_align_all_variants() {
2207 let left = TextAlign::Left;
2208 let right = TextAlign::Right;
2209 let justify = TextAlign::Justify;
2210 let center = TextAlign::Center;
2211
2212 assert!(matches!(left, TextAlign::Left));
2213 assert!(matches!(right, TextAlign::Right));
2214 assert!(matches!(justify, TextAlign::Justify));
2215 assert!(matches!(center, TextAlign::Center));
2216 }
2217
2218 #[test]
2219 fn test_text_align_debug() {
2220 assert_eq!(format!("{:?}", TextAlign::Left), "Left");
2221 assert_eq!(format!("{:?}", TextAlign::Right), "Right");
2222 assert_eq!(format!("{:?}", TextAlign::Justify), "Justify");
2223 assert_eq!(format!("{:?}", TextAlign::Center), "Center");
2224 }
2225 }
2226}