1use std::{collections::HashMap, 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, Default)]
336pub struct MetadataSheet {
337 pub contributor: Vec<String>,
339 pub creator: Vec<String>,
341 pub date: HashMap<String, String>,
343 pub identifier: HashMap<String, String>,
345 pub language: Vec<String>,
347 pub relation: Vec<String>,
349 pub subject: Vec<String>,
351 pub title: Vec<String>,
353
354 pub coverage: String,
356 pub description: String,
358 pub format: String,
360 pub publisher: String,
362 pub rights: String,
364 pub source: String,
366 pub epub_type: String,
368}
369
370impl MetadataSheet {
371 pub fn new() -> Self {
373 Self {
374 contributor: Vec::new(),
375 creator: Vec::new(),
376 date: HashMap::new(),
377 identifier: HashMap::new(),
378 language: Vec::new(),
379 relation: Vec::new(),
380 subject: Vec::new(),
381 title: Vec::new(),
382
383 coverage: String::new(),
384 description: String::new(),
385 format: String::new(),
386 publisher: String::new(),
387 rights: String::new(),
388 source: String::new(),
389 epub_type: String::new(),
390 }
391 }
392}
393
394#[cfg(feature = "builder")]
395impl MetadataSheet {
396 pub fn append_contributor(&mut self, contributor: impl Into<String>) -> &mut Self {
398 self.contributor.push(contributor.into());
399 self
400 }
401
402 pub fn append_creator(&mut self, creator: impl Into<String>) -> &mut Self {
404 self.creator.push(creator.into());
405 self
406 }
407
408 pub fn append_language(&mut self, language: impl Into<String>) -> &mut Self {
410 self.language.push(language.into());
411 self
412 }
413
414 pub fn append_relation(&mut self, relation: impl Into<String>) -> &mut Self {
416 self.relation.push(relation.into());
417 self
418 }
419
420 pub fn append_subject(&mut self, subject: impl Into<String>) -> &mut Self {
422 self.subject.push(subject.into());
423 self
424 }
425
426 pub fn append_title(&mut self, title: impl Into<String>) -> &mut Self {
428 self.title.push(title.into());
429 self
430 }
431
432 pub fn append_date(&mut self, date: impl Into<String>, event: impl Into<String>) -> &mut Self {
441 self.date.insert(date.into(), event.into());
442 self
443 }
444
445 pub fn append_identifier(
447 &mut self,
448 id: impl Into<String>,
449 value: impl Into<String>,
450 ) -> &mut Self {
451 self.identifier.insert(id.into(), value.into());
452 self
453 }
454
455 pub fn with_coverage(&mut self, coverage: impl Into<String>) -> &mut Self {
457 self.coverage = coverage.into();
458 self
459 }
460
461 pub fn with_description(&mut self, description: impl Into<String>) -> &mut Self {
463 self.description = description.into();
464 self
465 }
466
467 pub fn with_format(&mut self, format: impl Into<String>) -> &mut Self {
469 self.format = format.into();
470 self
471 }
472
473 pub fn with_publisher(&mut self, publisher: impl Into<String>) -> &mut Self {
475 self.publisher = publisher.into();
476 self
477 }
478
479 pub fn with_rights(&mut self, rights: impl Into<String>) -> &mut Self {
481 self.rights = rights.into();
482 self
483 }
484
485 pub fn with_source(&mut self, source: impl Into<String>) -> &mut Self {
487 self.source = source.into();
488 self
489 }
490
491 pub fn with_epub_type(&mut self, epub_type: impl Into<String>) -> &mut Self {
493 self.epub_type = epub_type.into();
494 self
495 }
496
497 pub fn build(&self) -> MetadataSheet {
499 MetadataSheet {
500 contributor: self.contributor.clone(),
501 creator: self.creator.clone(),
502 date: self.date.clone(),
503 identifier: self.identifier.clone(),
504 language: self.language.clone(),
505 relation: self.relation.clone(),
506 subject: self.subject.clone(),
507 title: self.title.clone(),
508 coverage: self.coverage.clone(),
509 description: self.description.clone(),
510 format: self.format.clone(),
511 publisher: self.publisher.clone(),
512 rights: self.rights.clone(),
513 source: self.source.clone(),
514 epub_type: self.epub_type.clone(),
515 }
516 }
517}
518
519#[cfg(feature = "builder")]
520impl From<MetadataSheet> for Vec<MetadataItem> {
521 fn from(sheet: MetadataSheet) -> Vec<MetadataItem> {
527 let mut items = Vec::new();
528
529 for title in &sheet.title {
532 items.push(MetadataItem::new("title", title));
533 }
534
535 for creator in &sheet.creator {
536 items.push(MetadataItem::new("creator", creator));
537 }
538
539 for contributor in &sheet.contributor {
540 items.push(MetadataItem::new("contributor", contributor));
541 }
542
543 for subject in &sheet.subject {
544 items.push(MetadataItem::new("subject", subject));
545 }
546
547 for language in &sheet.language {
548 items.push(MetadataItem::new("language", language));
549 }
550
551 for relation in &sheet.relation {
552 items.push(MetadataItem::new("relation", relation));
553 }
554
555 for (date, event) in &sheet.date {
559 let mut item = MetadataItem::new("date", date);
560 if !event.is_empty() {
561 let refinement_id = format!("date-{}", items.len());
562 item.id = Some(refinement_id.clone());
563 item.refined
564 .push(MetadataRefinement::new(&refinement_id, "event", event));
565 }
566 items.push(item);
567 }
568
569 for (id, value) in &sheet.identifier {
570 let mut item = MetadataItem::new("identifier", value);
571 if !id.is_empty() {
572 item.id = Some(id.clone());
573 }
574 items.push(item);
575 }
576
577 if !sheet.description.is_empty() {
580 items.push(MetadataItem::new("description", &sheet.description));
581 }
582
583 if !sheet.format.is_empty() {
584 items.push(MetadataItem::new("format", &sheet.format));
585 }
586
587 if !sheet.publisher.is_empty() {
588 items.push(MetadataItem::new("publisher", &sheet.publisher));
589 }
590
591 if !sheet.rights.is_empty() {
592 items.push(MetadataItem::new("rights", &sheet.rights));
593 }
594
595 if !sheet.source.is_empty() {
596 items.push(MetadataItem::new("source", &sheet.source));
597 }
598
599 if !sheet.coverage.is_empty() {
600 items.push(MetadataItem::new("coverage", &sheet.coverage));
601 }
602
603 if !sheet.epub_type.is_empty() {
604 items.push(MetadataItem::new("type", &sheet.epub_type));
605 }
606
607 items
608 }
609}
610
611#[derive(Debug, Clone)]
642pub struct ManifestItem {
643 pub id: String,
645
646 pub path: PathBuf,
652
653 pub mime: String,
655
656 pub properties: Option<String>,
662
663 pub fallback: Option<String>,
674}
675
676#[cfg(feature = "builder")]
677impl ManifestItem {
678 pub fn new(id: &str, path: &str) -> Result<Self, EpubError> {
689 if path.starts_with("../") {
690 return Err(
691 EpubBuilderError::IllegalManifestPath { manifest_id: id.to_string() }.into(),
692 );
693 }
694
695 Ok(Self {
696 id: id.to_string(),
697 path: PathBuf::from(path),
698 mime: String::new(),
699 properties: None,
700 fallback: None,
701 })
702 }
703
704 pub(crate) fn set_mime(self, mime: &str) -> Self {
706 Self {
707 id: self.id,
708 path: self.path,
709 mime: mime.to_string(),
710 properties: self.properties,
711 fallback: self.fallback,
712 }
713 }
714
715 pub fn append_property(&mut self, property: &str) -> &mut Self {
722 let new_properties = if let Some(properties) = &self.properties {
723 format!("{} {}", properties, property)
724 } else {
725 property.to_string()
726 };
727
728 self.properties = Some(new_properties);
729 self
730 }
731
732 pub fn with_fallback(&mut self, fallback: &str) -> &mut Self {
739 self.fallback = Some(fallback.to_string());
740 self
741 }
742
743 pub fn build(&self) -> Self {
747 Self { ..self.clone() }
748 }
749
750 pub fn attributes(&self) -> Vec<(&str, &str)> {
752 let mut attributes = Vec::new();
753
754 attributes.push(("id", self.id.as_str()));
755 attributes.push(("href", self.path.to_str().unwrap()));
756 attributes.push(("media-type", self.mime.as_str()));
757
758 if let Some(properties) = &self.properties {
759 attributes.push(("properties", properties.as_str()));
760 }
761
762 if let Some(fallback) = &self.fallback {
763 attributes.push(("fallback", fallback.as_str()));
764 }
765
766 attributes
767 }
768}
769
770#[derive(Debug, Clone)]
797pub struct SpineItem {
798 pub idref: String,
804
805 pub id: Option<String>,
807
808 pub properties: Option<String>,
814
815 pub linear: bool,
825}
826
827#[cfg(feature = "builder")]
828impl SpineItem {
829 pub fn new(idref: &str) -> Self {
838 Self {
839 idref: idref.to_string(),
840 id: None,
841 properties: None,
842 linear: true,
843 }
844 }
845
846 pub fn with_id(&mut self, id: &str) -> &mut Self {
853 self.id = Some(id.to_string());
854 self
855 }
856
857 pub fn append_property(&mut self, property: &str) -> &mut Self {
864 let new_properties = if let Some(properties) = &self.properties {
865 format!("{} {}", properties, property)
866 } else {
867 property.to_string()
868 };
869
870 self.properties = Some(new_properties);
871 self
872 }
873
874 pub fn set_linear(&mut self, linear: bool) -> &mut Self {
881 self.linear = linear;
882 self
883 }
884
885 pub fn build(&self) -> Self {
889 Self { ..self.clone() }
890 }
891
892 pub(crate) fn attributes(&self) -> Vec<(&str, &str)> {
894 let mut attributes = Vec::new();
895
896 attributes.push(("idref", self.idref.as_str()));
897 attributes.push(("linear", if self.linear { "yes" } else { "no" }));
898
899 if let Some(id) = &self.id {
900 attributes.push(("id", id.as_str()));
901 }
902
903 if let Some(properties) = &self.properties {
904 attributes.push(("properties", properties.as_str()));
905 }
906
907 attributes
908 }
909}
910
911#[derive(Debug, Clone)]
917pub struct EncryptionData {
918 pub method: String,
925
926 pub data: String,
931}
932
933#[derive(Debug, Eq, Clone)]
958pub struct NavPoint {
959 pub label: String,
963
964 pub content: Option<PathBuf>,
969
970 pub children: Vec<NavPoint>,
972
973 pub play_order: Option<usize>,
978}
979
980#[cfg(feature = "builder")]
981impl NavPoint {
982 pub fn new(label: &str) -> Self {
989 Self {
990 label: label.to_string(),
991 content: None,
992 children: vec![],
993 play_order: None,
994 }
995 }
996
997 pub fn with_content(&mut self, content: &str) -> &mut Self {
1004 self.content = Some(PathBuf::from(content));
1005 self
1006 }
1007
1008 pub fn append_child(&mut self, child: NavPoint) -> &mut Self {
1015 self.children.push(child);
1016 self
1017 }
1018
1019 pub fn set_children(&mut self, children: Vec<NavPoint>) -> &mut Self {
1026 self.children = children;
1027 self
1028 }
1029
1030 pub fn build(&self) -> Self {
1034 Self { ..self.clone() }
1035 }
1036}
1037
1038impl Ord for NavPoint {
1039 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1040 self.play_order.cmp(&other.play_order)
1041 }
1042}
1043
1044impl PartialOrd for NavPoint {
1045 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1046 Some(self.cmp(other))
1047 }
1048}
1049
1050impl PartialEq for NavPoint {
1051 fn eq(&self, other: &Self) -> bool {
1052 self.play_order == other.play_order
1053 }
1054}
1055
1056#[cfg(feature = "content-builder")]
1061#[derive(Debug, Clone, Eq, PartialEq)]
1062pub struct Footnote {
1063 pub locate: usize,
1065
1066 pub content: String,
1068}
1069
1070#[cfg(feature = "content-builder")]
1071impl Ord for Footnote {
1072 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1073 self.locate.cmp(&other.locate)
1074 }
1075}
1076
1077#[cfg(feature = "content-builder")]
1078impl PartialOrd for Footnote {
1079 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1080 Some(self.cmp(other))
1081 }
1082}
1083
1084#[cfg(feature = "content-builder")]
1086#[derive(Debug, Copy, Clone)]
1087pub enum BlockType {
1088 Text,
1092
1093 Quote,
1098
1099 Title,
1103
1104 Image,
1108
1109 Audio,
1113
1114 Video,
1118
1119 MathML,
1124}
1125
1126#[cfg(feature = "content-builder")]
1127impl std::fmt::Display for BlockType {
1128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1129 match self {
1130 BlockType::Text => write!(f, "Text"),
1131 BlockType::Quote => write!(f, "Quote"),
1132 BlockType::Title => write!(f, "Title"),
1133 BlockType::Image => write!(f, "Image"),
1134 BlockType::Audio => write!(f, "Audio"),
1135 BlockType::Video => write!(f, "Video"),
1136 BlockType::MathML => write!(f, "MathML"),
1137 }
1138 }
1139}
1140
1141#[cfg(feature = "content-builder")]
1146#[derive(Debug, Default, Clone)]
1147pub struct StyleOptions {
1148 pub text: TextStyle,
1150
1151 pub color_scheme: ColorScheme,
1155
1156 pub layout: PageLayout,
1160}
1161
1162#[cfg(feature = "content-builder")]
1163impl StyleOptions {
1164 pub fn new() -> Self {
1166 Self::default()
1167 }
1168
1169 pub fn with_text(&mut self, text: TextStyle) -> &mut Self {
1171 self.text = text;
1172 self
1173 }
1174
1175 pub fn with_color_scheme(&mut self, color_scheme: ColorScheme) -> &mut Self {
1177 self.color_scheme = color_scheme;
1178 self
1179 }
1180
1181 pub fn with_layout(&mut self, layout: PageLayout) -> &mut Self {
1183 self.layout = layout;
1184 self
1185 }
1186
1187 pub fn build(&self) -> Self {
1189 Self { ..self.clone() }
1190 }
1191}
1192
1193#[cfg(feature = "content-builder")]
1198#[derive(Debug, Clone)]
1199pub struct TextStyle {
1200 pub font_size: f32,
1205
1206 pub line_height: f32,
1212
1213 pub font_family: String,
1218
1219 pub font_weight: String,
1224
1225 pub font_style: String,
1230
1231 pub letter_spacing: String,
1236
1237 pub text_indent: f32,
1242}
1243
1244#[cfg(feature = "content-builder")]
1245impl Default for TextStyle {
1246 fn default() -> Self {
1247 Self {
1248 font_size: 1.0,
1249 line_height: 1.6,
1250 font_family: "-apple-system, Roboto, sans-serif".to_string(),
1251 font_weight: "normal".to_string(),
1252 font_style: "normal".to_string(),
1253 letter_spacing: "normal".to_string(),
1254 text_indent: 2.0,
1255 }
1256 }
1257}
1258
1259#[cfg(feature = "content-builder")]
1260impl TextStyle {
1261 pub fn new() -> Self {
1263 Self::default()
1264 }
1265
1266 pub fn with_font_size(&mut self, font_size: f32) -> &mut Self {
1268 self.font_size = font_size;
1269 self
1270 }
1271
1272 pub fn with_line_height(&mut self, line_height: f32) -> &mut Self {
1274 self.line_height = line_height;
1275 self
1276 }
1277
1278 pub fn with_font_family(&mut self, font_family: &str) -> &mut Self {
1280 self.font_family = font_family.to_string();
1281 self
1282 }
1283
1284 pub fn with_font_weight(&mut self, font_weight: &str) -> &mut Self {
1286 self.font_weight = font_weight.to_string();
1287 self
1288 }
1289
1290 pub fn with_font_style(&mut self, font_style: &str) -> &mut Self {
1292 self.font_style = font_style.to_string();
1293 self
1294 }
1295
1296 pub fn with_letter_spacing(&mut self, letter_spacing: &str) -> &mut Self {
1298 self.letter_spacing = letter_spacing.to_string();
1299 self
1300 }
1301
1302 pub fn with_text_indent(&mut self, text_indent: f32) -> &mut Self {
1304 self.text_indent = text_indent;
1305 self
1306 }
1307
1308 pub fn build(&self) -> Self {
1310 Self { ..self.clone() }
1311 }
1312}
1313
1314#[cfg(feature = "content-builder")]
1319#[derive(Debug, Clone)]
1320pub struct ColorScheme {
1321 pub background: String,
1326
1327 pub text: String,
1332
1333 pub link: String,
1338}
1339
1340#[cfg(feature = "content-builder")]
1341impl Default for ColorScheme {
1342 fn default() -> Self {
1343 Self {
1344 background: "#FFFFFF".to_string(),
1345 text: "#000000".to_string(),
1346 link: "#6f6f6f".to_string(),
1347 }
1348 }
1349}
1350
1351#[cfg(feature = "content-builder")]
1352impl ColorScheme {
1353 pub fn new() -> Self {
1355 Self::default()
1356 }
1357
1358 pub fn with_background(&mut self, background: &str) -> &mut Self {
1360 self.background = background.to_string();
1361 self
1362 }
1363
1364 pub fn with_text(&mut self, text: &str) -> &mut Self {
1366 self.text = text.to_string();
1367 self
1368 }
1369
1370 pub fn with_link(&mut self, link: &str) -> &mut Self {
1372 self.link = link.to_string();
1373 self
1374 }
1375
1376 pub fn build(&self) -> Self {
1378 Self { ..self.clone() }
1379 }
1380}
1381
1382#[cfg(feature = "content-builder")]
1387#[derive(Debug, Clone)]
1388pub struct PageLayout {
1389 pub margin: usize,
1393
1394 pub text_align: TextAlign,
1398
1399 pub paragraph_spacing: usize,
1403}
1404
1405#[cfg(feature = "content-builder")]
1406impl Default for PageLayout {
1407 fn default() -> Self {
1408 Self {
1409 margin: 20,
1410 text_align: Default::default(),
1411 paragraph_spacing: 16,
1412 }
1413 }
1414}
1415
1416#[cfg(feature = "content-builder")]
1417impl PageLayout {
1418 pub fn new() -> Self {
1420 Self::default()
1421 }
1422
1423 pub fn with_margin(&mut self, margin: usize) -> &mut Self {
1425 self.margin = margin;
1426 self
1427 }
1428
1429 pub fn with_text_align(&mut self, text_align: TextAlign) -> &mut Self {
1431 self.text_align = text_align;
1432 self
1433 }
1434
1435 pub fn with_paragraph_spacing(&mut self, paragraph_spacing: usize) -> &mut Self {
1437 self.paragraph_spacing = paragraph_spacing;
1438 self
1439 }
1440
1441 pub fn build(&self) -> Self {
1443 Self { ..self.clone() }
1444 }
1445}
1446
1447#[cfg(feature = "content-builder")]
1451#[derive(Debug, Default, Clone, Copy, PartialEq)]
1452pub enum TextAlign {
1453 #[default]
1457 Left,
1458
1459 Right,
1463
1464 Justify,
1469
1470 Center,
1474}
1475
1476#[cfg(feature = "content-builder")]
1477impl std::fmt::Display for TextAlign {
1478 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1479 match self {
1480 TextAlign::Left => write!(f, "left"),
1481 TextAlign::Right => write!(f, "right"),
1482 TextAlign::Justify => write!(f, "justify"),
1483 TextAlign::Center => write!(f, "center"),
1484 }
1485 }
1486}
1487
1488#[cfg(test)]
1489mod tests {
1490 mod navpoint_tests {
1491 use std::path::PathBuf;
1492
1493 use crate::types::NavPoint;
1494
1495 #[test]
1497 fn test_navpoint_partial_eq() {
1498 let nav1 = NavPoint {
1499 label: "Chapter 1".to_string(),
1500 content: Some(PathBuf::from("chapter1.html")),
1501 children: vec![],
1502 play_order: Some(1),
1503 };
1504
1505 let nav2 = NavPoint {
1506 label: "Chapter 1".to_string(),
1507 content: Some(PathBuf::from("chapter2.html")),
1508 children: vec![],
1509 play_order: Some(1),
1510 };
1511
1512 let nav3 = NavPoint {
1513 label: "Chapter 2".to_string(),
1514 content: Some(PathBuf::from("chapter1.html")),
1515 children: vec![],
1516 play_order: Some(2),
1517 };
1518
1519 assert_eq!(nav1, nav2); assert_ne!(nav1, nav3); }
1522
1523 #[test]
1525 fn test_navpoint_ord() {
1526 let nav1 = NavPoint {
1527 label: "Chapter 1".to_string(),
1528 content: Some(PathBuf::from("chapter1.html")),
1529 children: vec![],
1530 play_order: Some(1),
1531 };
1532
1533 let nav2 = NavPoint {
1534 label: "Chapter 2".to_string(),
1535 content: Some(PathBuf::from("chapter2.html")),
1536 children: vec![],
1537 play_order: Some(2),
1538 };
1539
1540 let nav3 = NavPoint {
1541 label: "Chapter 3".to_string(),
1542 content: Some(PathBuf::from("chapter3.html")),
1543 children: vec![],
1544 play_order: Some(3),
1545 };
1546
1547 assert!(nav1 < nav2);
1549 assert!(nav2 > nav1);
1550 assert!(nav1 == nav1);
1551
1552 assert_eq!(nav1.partial_cmp(&nav2), Some(std::cmp::Ordering::Less));
1554 assert_eq!(nav2.partial_cmp(&nav1), Some(std::cmp::Ordering::Greater));
1555 assert_eq!(nav1.partial_cmp(&nav1), Some(std::cmp::Ordering::Equal));
1556
1557 let mut nav_points = vec![nav2.clone(), nav3.clone(), nav1.clone()];
1559 nav_points.sort();
1560 assert_eq!(nav_points, vec![nav1, nav2, nav3]);
1561 }
1562
1563 #[test]
1565 fn test_navpoint_ord_with_none_play_order() {
1566 let nav_with_order = NavPoint {
1567 label: "Chapter 1".to_string(),
1568 content: Some(PathBuf::from("chapter1.html")),
1569 children: vec![],
1570 play_order: Some(1),
1571 };
1572
1573 let nav_without_order = NavPoint {
1574 label: "Preface".to_string(),
1575 content: Some(PathBuf::from("preface.html")),
1576 children: vec![],
1577 play_order: None,
1578 };
1579
1580 assert!(nav_without_order < nav_with_order);
1581 assert!(nav_with_order > nav_without_order);
1582
1583 let nav_without_order2 = NavPoint {
1584 label: "Introduction".to_string(),
1585 content: Some(PathBuf::from("intro.html")),
1586 children: vec![],
1587 play_order: None,
1588 };
1589
1590 assert!(nav_without_order == nav_without_order2);
1591 }
1592
1593 #[test]
1595 fn test_navpoint_with_children() {
1596 let child1 = NavPoint {
1597 label: "Section 1.1".to_string(),
1598 content: Some(PathBuf::from("section1_1.html")),
1599 children: vec![],
1600 play_order: Some(1),
1601 };
1602
1603 let child2 = NavPoint {
1604 label: "Section 1.2".to_string(),
1605 content: Some(PathBuf::from("section1_2.html")),
1606 children: vec![],
1607 play_order: Some(2),
1608 };
1609
1610 let parent1 = NavPoint {
1611 label: "Chapter 1".to_string(),
1612 content: Some(PathBuf::from("chapter1.html")),
1613 children: vec![child1.clone(), child2.clone()],
1614 play_order: Some(1),
1615 };
1616
1617 let parent2 = NavPoint {
1618 label: "Chapter 1".to_string(),
1619 content: Some(PathBuf::from("chapter1.html")),
1620 children: vec![child1.clone(), child2.clone()],
1621 play_order: Some(1),
1622 };
1623
1624 assert!(parent1 == parent2);
1625
1626 let parent3 = NavPoint {
1627 label: "Chapter 2".to_string(),
1628 content: Some(PathBuf::from("chapter2.html")),
1629 children: vec![child1.clone(), child2.clone()],
1630 play_order: Some(2),
1631 };
1632
1633 assert!(parent1 != parent3);
1634 assert!(parent1 < parent3);
1635 }
1636
1637 #[test]
1639 fn test_navpoint_with_none_content() {
1640 let nav1 = NavPoint {
1641 label: "Chapter 1".to_string(),
1642 content: None,
1643 children: vec![],
1644 play_order: Some(1),
1645 };
1646
1647 let nav2 = NavPoint {
1648 label: "Chapter 1".to_string(),
1649 content: None,
1650 children: vec![],
1651 play_order: Some(1),
1652 };
1653
1654 assert!(nav1 == nav2);
1655 }
1656 }
1657
1658 #[cfg(feature = "builder")]
1659 mod builder_tests {
1660 mod metadata_item {
1661 use crate::types::{MetadataItem, MetadataRefinement};
1662
1663 #[test]
1664 fn test_metadata_item_new() {
1665 let metadata_item = MetadataItem::new("title", "EPUB Test Book");
1666
1667 assert_eq!(metadata_item.property, "title");
1668 assert_eq!(metadata_item.value, "EPUB Test Book");
1669 assert_eq!(metadata_item.id, None);
1670 assert_eq!(metadata_item.lang, None);
1671 assert_eq!(metadata_item.refined.len(), 0);
1672 }
1673
1674 #[test]
1675 fn test_metadata_item_with_id() {
1676 let mut metadata_item = MetadataItem::new("creator", "John Doe");
1677 metadata_item.with_id("creator-1");
1678
1679 assert_eq!(metadata_item.property, "creator");
1680 assert_eq!(metadata_item.value, "John Doe");
1681 assert_eq!(metadata_item.id, Some("creator-1".to_string()));
1682 assert_eq!(metadata_item.lang, None);
1683 assert_eq!(metadata_item.refined.len(), 0);
1684 }
1685
1686 #[test]
1687 fn test_metadata_item_with_lang() {
1688 let mut metadata_item = MetadataItem::new("title", "测试书籍");
1689 metadata_item.with_lang("zh-CN");
1690
1691 assert_eq!(metadata_item.property, "title");
1692 assert_eq!(metadata_item.value, "测试书籍");
1693 assert_eq!(metadata_item.id, None);
1694 assert_eq!(metadata_item.lang, Some("zh-CN".to_string()));
1695 assert_eq!(metadata_item.refined.len(), 0);
1696 }
1697
1698 #[test]
1699 fn test_metadata_item_append_refinement() {
1700 let mut metadata_item = MetadataItem::new("creator", "John Doe");
1701 metadata_item.with_id("creator-1"); let refinement = MetadataRefinement::new("creator-1", "role", "author");
1704 metadata_item.append_refinement(refinement);
1705
1706 assert_eq!(metadata_item.refined.len(), 1);
1707 assert_eq!(metadata_item.refined[0].refines, "creator-1");
1708 assert_eq!(metadata_item.refined[0].property, "role");
1709 assert_eq!(metadata_item.refined[0].value, "author");
1710 }
1711
1712 #[test]
1713 fn test_metadata_item_append_refinement_without_id() {
1714 let mut metadata_item = MetadataItem::new("title", "Test Book");
1715 let refinement = MetadataRefinement::new("title", "title-type", "main");
1718 metadata_item.append_refinement(refinement);
1719
1720 assert_eq!(metadata_item.refined.len(), 0);
1722 }
1723
1724 #[test]
1725 fn test_metadata_item_build() {
1726 let mut metadata_item = MetadataItem::new("identifier", "urn:isbn:1234567890");
1727 metadata_item.with_id("pub-id").with_lang("en");
1728
1729 let built = metadata_item.build();
1730
1731 assert_eq!(built.property, "identifier");
1732 assert_eq!(built.value, "urn:isbn:1234567890");
1733 assert_eq!(built.id, Some("pub-id".to_string()));
1734 assert_eq!(built.lang, Some("en".to_string()));
1735 assert_eq!(built.refined.len(), 0);
1736 }
1737
1738 #[test]
1739 fn test_metadata_item_builder_chaining() {
1740 let mut metadata_item = MetadataItem::new("title", "EPUB 3.3 Guide");
1741 metadata_item.with_id("title").with_lang("en");
1742
1743 let refinement = MetadataRefinement::new("title", "title-type", "main");
1744 metadata_item.append_refinement(refinement);
1745
1746 let built = metadata_item.build();
1747
1748 assert_eq!(built.property, "title");
1749 assert_eq!(built.value, "EPUB 3.3 Guide");
1750 assert_eq!(built.id, Some("title".to_string()));
1751 assert_eq!(built.lang, Some("en".to_string()));
1752 assert_eq!(built.refined.len(), 1);
1753 }
1754
1755 #[test]
1756 fn test_metadata_item_attributes_dc_namespace() {
1757 let mut metadata_item = MetadataItem::new("title", "Test Book");
1758 metadata_item.with_id("title-id");
1759
1760 let attributes = metadata_item.attributes();
1761
1762 assert!(!attributes.iter().any(|(k, _)| k == &"property"));
1764 assert!(
1765 attributes
1766 .iter()
1767 .any(|(k, v)| k == &"id" && v == &"title-id")
1768 );
1769 }
1770
1771 #[test]
1772 fn test_metadata_item_attributes_non_dc_namespace() {
1773 let mut metadata_item = MetadataItem::new("meta", "value");
1774 metadata_item.with_id("meta-id");
1775
1776 let attributes = metadata_item.attributes();
1777
1778 assert!(attributes.iter().any(|(k, _)| k == &"property"));
1780 assert!(
1781 attributes
1782 .iter()
1783 .any(|(k, v)| k == &"id" && v == &"meta-id")
1784 );
1785 }
1786
1787 #[test]
1788 fn test_metadata_item_attributes_with_lang() {
1789 let mut metadata_item = MetadataItem::new("title", "Test Book");
1790 metadata_item.with_id("title-id").with_lang("en");
1791
1792 let attributes = metadata_item.attributes();
1793
1794 assert!(
1795 attributes
1796 .iter()
1797 .any(|(k, v)| k == &"id" && v == &"title-id")
1798 );
1799 assert!(attributes.iter().any(|(k, v)| k == &"lang" && v == &"en"));
1800 }
1801 }
1802
1803 mod metadata_refinement {
1804 use crate::types::MetadataRefinement;
1805
1806 #[test]
1807 fn test_metadata_refinement_new() {
1808 let refinement = MetadataRefinement::new("title", "title-type", "main");
1809
1810 assert_eq!(refinement.refines, "title");
1811 assert_eq!(refinement.property, "title-type");
1812 assert_eq!(refinement.value, "main");
1813 assert_eq!(refinement.lang, None);
1814 assert_eq!(refinement.scheme, None);
1815 }
1816
1817 #[test]
1818 fn test_metadata_refinement_with_lang() {
1819 let mut refinement = MetadataRefinement::new("creator", "role", "author");
1820 refinement.with_lang("en");
1821
1822 assert_eq!(refinement.refines, "creator");
1823 assert_eq!(refinement.property, "role");
1824 assert_eq!(refinement.value, "author");
1825 assert_eq!(refinement.lang, Some("en".to_string()));
1826 assert_eq!(refinement.scheme, None);
1827 }
1828
1829 #[test]
1830 fn test_metadata_refinement_with_scheme() {
1831 let mut refinement = MetadataRefinement::new("creator", "role", "author");
1832 refinement.with_scheme("marc:relators");
1833
1834 assert_eq!(refinement.refines, "creator");
1835 assert_eq!(refinement.property, "role");
1836 assert_eq!(refinement.value, "author");
1837 assert_eq!(refinement.lang, None);
1838 assert_eq!(refinement.scheme, Some("marc:relators".to_string()));
1839 }
1840
1841 #[test]
1842 fn test_metadata_refinement_build() {
1843 let mut refinement = MetadataRefinement::new("title", "alternate-script", "テスト");
1844 refinement.with_lang("ja").with_scheme("iso-15924");
1845
1846 let built = refinement.build();
1847
1848 assert_eq!(built.refines, "title");
1849 assert_eq!(built.property, "alternate-script");
1850 assert_eq!(built.value, "テスト");
1851 assert_eq!(built.lang, Some("ja".to_string()));
1852 assert_eq!(built.scheme, Some("iso-15924".to_string()));
1853 }
1854
1855 #[test]
1856 fn test_metadata_refinement_builder_chaining() {
1857 let mut refinement = MetadataRefinement::new("creator", "file-as", "Doe, John");
1858 refinement.with_lang("en").with_scheme("dcterms");
1859
1860 let built = refinement.build();
1861
1862 assert_eq!(built.refines, "creator");
1863 assert_eq!(built.property, "file-as");
1864 assert_eq!(built.value, "Doe, John");
1865 assert_eq!(built.lang, Some("en".to_string()));
1866 assert_eq!(built.scheme, Some("dcterms".to_string()));
1867 }
1868
1869 #[test]
1870 fn test_metadata_refinement_attributes() {
1871 let mut refinement = MetadataRefinement::new("title", "title-type", "main");
1872 refinement.with_lang("en").with_scheme("onix:codelist5");
1873
1874 let attributes = refinement.attributes();
1875
1876 assert!(
1877 attributes
1878 .iter()
1879 .any(|(k, v)| k == &"refines" && v == &"title")
1880 );
1881 assert!(
1882 attributes
1883 .iter()
1884 .any(|(k, v)| k == &"property" && v == &"title-type")
1885 );
1886 assert!(attributes.iter().any(|(k, v)| k == &"lang" && v == &"en"));
1887 assert!(
1888 attributes
1889 .iter()
1890 .any(|(k, v)| k == &"scheme" && v == &"onix:codelist5")
1891 );
1892 }
1893
1894 #[test]
1895 fn test_metadata_refinement_attributes_optional_fields() {
1896 let refinement = MetadataRefinement::new("creator", "role", "author");
1897 let attributes = refinement.attributes();
1898
1899 assert!(
1900 attributes
1901 .iter()
1902 .any(|(k, v)| k == &"refines" && v == &"creator")
1903 );
1904 assert!(
1905 attributes
1906 .iter()
1907 .any(|(k, v)| k == &"property" && v == &"role")
1908 );
1909
1910 assert!(!attributes.iter().any(|(k, _)| k == &"lang"));
1912 assert!(!attributes.iter().any(|(k, _)| k == &"scheme"));
1913 }
1914 }
1915
1916 mod manifest_item {
1917 use std::path::PathBuf;
1918
1919 use crate::types::ManifestItem;
1920
1921 #[test]
1922 fn test_manifest_item_new() {
1923 let manifest_item = ManifestItem::new("cover", "images/cover.jpg");
1924 assert!(manifest_item.is_ok());
1925
1926 let manifest_item = manifest_item.unwrap();
1927 assert_eq!(manifest_item.id, "cover");
1928 assert_eq!(manifest_item.path, PathBuf::from("images/cover.jpg"));
1929 assert_eq!(manifest_item.mime, "");
1930 assert_eq!(manifest_item.properties, None);
1931 assert_eq!(manifest_item.fallback, None);
1932 }
1933
1934 #[test]
1935 fn test_manifest_item_append_property() {
1936 let manifest_item = ManifestItem::new("nav", "nav.xhtml");
1937 assert!(manifest_item.is_ok());
1938
1939 let mut manifest_item = manifest_item.unwrap();
1940 manifest_item.append_property("nav");
1941
1942 assert_eq!(manifest_item.id, "nav");
1943 assert_eq!(manifest_item.path, PathBuf::from("nav.xhtml"));
1944 assert_eq!(manifest_item.mime, "");
1945 assert_eq!(manifest_item.properties, Some("nav".to_string()));
1946 assert_eq!(manifest_item.fallback, None);
1947 }
1948
1949 #[test]
1950 fn test_manifest_item_append_multiple_properties() {
1951 let manifest_item = ManifestItem::new("content", "content.xhtml");
1952 assert!(manifest_item.is_ok());
1953
1954 let mut manifest_item = manifest_item.unwrap();
1955 manifest_item
1956 .append_property("nav")
1957 .append_property("scripted")
1958 .append_property("svg");
1959
1960 assert_eq!(
1961 manifest_item.properties,
1962 Some("nav scripted svg".to_string())
1963 );
1964 }
1965
1966 #[test]
1967 fn test_manifest_item_with_fallback() {
1968 let manifest_item = ManifestItem::new("image", "image.tiff");
1969 assert!(manifest_item.is_ok());
1970
1971 let mut manifest_item = manifest_item.unwrap();
1972 manifest_item.with_fallback("image-fallback");
1973
1974 assert_eq!(manifest_item.id, "image");
1975 assert_eq!(manifest_item.path, PathBuf::from("image.tiff"));
1976 assert_eq!(manifest_item.mime, "");
1977 assert_eq!(manifest_item.properties, None);
1978 assert_eq!(manifest_item.fallback, Some("image-fallback".to_string()));
1979 }
1980
1981 #[test]
1982 fn test_manifest_item_set_mime() {
1983 let manifest_item = ManifestItem::new("style", "style.css");
1984 assert!(manifest_item.is_ok());
1985
1986 let manifest_item = manifest_item.unwrap();
1987 let updated_item = manifest_item.set_mime("text/css");
1988
1989 assert_eq!(updated_item.id, "style");
1990 assert_eq!(updated_item.path, PathBuf::from("style.css"));
1991 assert_eq!(updated_item.mime, "text/css");
1992 assert_eq!(updated_item.properties, None);
1993 assert_eq!(updated_item.fallback, None);
1994 }
1995
1996 #[test]
1997 fn test_manifest_item_build() {
1998 let manifest_item = ManifestItem::new("cover", "images/cover.jpg");
1999 assert!(manifest_item.is_ok());
2000
2001 let mut manifest_item = manifest_item.unwrap();
2002 manifest_item
2003 .append_property("cover-image")
2004 .with_fallback("cover-fallback");
2005
2006 let built = manifest_item.build();
2007
2008 assert_eq!(built.id, "cover");
2009 assert_eq!(built.path, PathBuf::from("images/cover.jpg"));
2010 assert_eq!(built.mime, "");
2011 assert_eq!(built.properties, Some("cover-image".to_string()));
2012 assert_eq!(built.fallback, Some("cover-fallback".to_string()));
2013 }
2014
2015 #[test]
2016 fn test_manifest_item_builder_chaining() {
2017 let manifest_item = ManifestItem::new("content", "content.xhtml");
2018 assert!(manifest_item.is_ok());
2019
2020 let mut manifest_item = manifest_item.unwrap();
2021 manifest_item
2022 .append_property("scripted")
2023 .append_property("svg")
2024 .with_fallback("fallback-content");
2025
2026 let built = manifest_item.build();
2027
2028 assert_eq!(built.id, "content");
2029 assert_eq!(built.path, PathBuf::from("content.xhtml"));
2030 assert_eq!(built.mime, "");
2031 assert_eq!(built.properties, Some("scripted svg".to_string()));
2032 assert_eq!(built.fallback, Some("fallback-content".to_string()));
2033 }
2034
2035 #[test]
2036 fn test_manifest_item_attributes() {
2037 let manifest_item = ManifestItem::new("nav", "nav.xhtml");
2038 assert!(manifest_item.is_ok());
2039
2040 let mut manifest_item = manifest_item.unwrap();
2041 manifest_item
2042 .append_property("nav")
2043 .with_fallback("fallback-nav");
2044
2045 let manifest_item = manifest_item.set_mime("application/xhtml+xml");
2047 let attributes = manifest_item.attributes();
2048
2049 assert!(attributes.contains(&("id", "nav")));
2051 assert!(attributes.contains(&("href", "nav.xhtml")));
2052 assert!(attributes.contains(&("media-type", "application/xhtml+xml")));
2053 assert!(attributes.contains(&("properties", "nav")));
2054 assert!(attributes.contains(&("fallback", "fallback-nav")));
2055 }
2056
2057 #[test]
2058 fn test_manifest_item_attributes_optional_fields() {
2059 let manifest_item = ManifestItem::new("simple", "simple.xhtml");
2060 assert!(manifest_item.is_ok());
2061
2062 let manifest_item = manifest_item.unwrap();
2063 let manifest_item = manifest_item.set_mime("application/xhtml+xml");
2064 let attributes = manifest_item.attributes();
2065
2066 assert!(attributes.contains(&("id", "simple")));
2068 assert!(attributes.contains(&("href", "simple.xhtml")));
2069 assert!(attributes.contains(&("media-type", "application/xhtml+xml")));
2070
2071 assert!(!attributes.iter().any(|(k, _)| k == &"properties"));
2073 assert!(!attributes.iter().any(|(k, _)| k == &"fallback"));
2074 }
2075
2076 #[test]
2077 fn test_manifest_item_path_handling() {
2078 let manifest_item = ManifestItem::new("test", "../images/test.png");
2079 assert!(manifest_item.is_err());
2080
2081 let err = manifest_item.unwrap_err();
2082 assert_eq!(
2083 err.to_string(),
2084 "Epub builder error: A manifest with id 'test' should not use a relative path starting with '../'."
2085 );
2086 }
2087 }
2088
2089 mod spine_item {
2090 use crate::types::SpineItem;
2091
2092 #[test]
2093 fn test_spine_item_new() {
2094 let spine_item = SpineItem::new("content_001");
2095
2096 assert_eq!(spine_item.idref, "content_001");
2097 assert_eq!(spine_item.id, None);
2098 assert_eq!(spine_item.properties, None);
2099 assert_eq!(spine_item.linear, true);
2100 }
2101
2102 #[test]
2103 fn test_spine_item_with_id() {
2104 let mut spine_item = SpineItem::new("content_001");
2105 spine_item.with_id("spine1");
2106
2107 assert_eq!(spine_item.idref, "content_001");
2108 assert_eq!(spine_item.id, Some("spine1".to_string()));
2109 assert_eq!(spine_item.properties, None);
2110 assert_eq!(spine_item.linear, true);
2111 }
2112
2113 #[test]
2114 fn test_spine_item_append_property() {
2115 let mut spine_item = SpineItem::new("content_001");
2116 spine_item.append_property("page-spread-left");
2117
2118 assert_eq!(spine_item.idref, "content_001");
2119 assert_eq!(spine_item.id, None);
2120 assert_eq!(spine_item.properties, Some("page-spread-left".to_string()));
2121 assert_eq!(spine_item.linear, true);
2122 }
2123
2124 #[test]
2125 fn test_spine_item_append_multiple_properties() {
2126 let mut spine_item = SpineItem::new("content_001");
2127 spine_item
2128 .append_property("page-spread-left")
2129 .append_property("rendition:layout-pre-paginated");
2130
2131 assert_eq!(
2132 spine_item.properties,
2133 Some("page-spread-left rendition:layout-pre-paginated".to_string())
2134 );
2135 }
2136
2137 #[test]
2138 fn test_spine_item_set_linear() {
2139 let mut spine_item = SpineItem::new("content_001");
2140 spine_item.set_linear(false);
2141
2142 assert_eq!(spine_item.idref, "content_001");
2143 assert_eq!(spine_item.id, None);
2144 assert_eq!(spine_item.properties, None);
2145 assert_eq!(spine_item.linear, false);
2146 }
2147
2148 #[test]
2149 fn test_spine_item_build() {
2150 let mut spine_item = SpineItem::new("content_001");
2151 spine_item
2152 .with_id("spine1")
2153 .append_property("page-spread-left")
2154 .set_linear(false);
2155
2156 let built = spine_item.build();
2157
2158 assert_eq!(built.idref, "content_001");
2159 assert_eq!(built.id, Some("spine1".to_string()));
2160 assert_eq!(built.properties, Some("page-spread-left".to_string()));
2161 assert_eq!(built.linear, false);
2162 }
2163
2164 #[test]
2165 fn test_spine_item_builder_chaining() {
2166 let mut spine_item = SpineItem::new("content_001");
2167 spine_item
2168 .with_id("spine1")
2169 .append_property("page-spread-left")
2170 .set_linear(false);
2171
2172 let built = spine_item.build();
2173
2174 assert_eq!(built.idref, "content_001");
2175 assert_eq!(built.id, Some("spine1".to_string()));
2176 assert_eq!(built.properties, Some("page-spread-left".to_string()));
2177 assert_eq!(built.linear, false);
2178 }
2179
2180 #[test]
2181 fn test_spine_item_attributes() {
2182 let mut spine_item = SpineItem::new("content_001");
2183 spine_item
2184 .with_id("spine1")
2185 .append_property("page-spread-left")
2186 .set_linear(false);
2187
2188 let attributes = spine_item.attributes();
2189
2190 assert!(attributes.contains(&("idref", "content_001")));
2192 assert!(attributes.contains(&("id", "spine1")));
2193 assert!(attributes.contains(&("properties", "page-spread-left")));
2194 assert!(attributes.contains(&("linear", "no"))); }
2196
2197 #[test]
2198 fn test_spine_item_attributes_linear_yes() {
2199 let spine_item = SpineItem::new("content_001");
2200 let attributes = spine_item.attributes();
2201
2202 assert!(attributes.contains(&("linear", "yes")));
2204 }
2205
2206 #[test]
2207 fn test_spine_item_attributes_optional_fields() {
2208 let spine_item = SpineItem::new("content_001");
2209 let attributes = spine_item.attributes();
2210
2211 assert!(attributes.contains(&("idref", "content_001")));
2213 assert!(attributes.contains(&("linear", "yes")));
2214
2215 assert!(!attributes.iter().any(|(k, _)| k == &"id"));
2217 assert!(!attributes.iter().any(|(k, _)| k == &"properties"));
2218 }
2219 }
2220
2221 mod metadata_sheet {
2222 use crate::types::{MetadataItem, MetadataSheet};
2223
2224 #[test]
2225 fn test_metadata_sheet_new() {
2226 let sheet = MetadataSheet::new();
2227
2228 assert!(sheet.contributor.is_empty());
2229 assert!(sheet.creator.is_empty());
2230 assert!(sheet.date.is_empty());
2231 assert!(sheet.identifier.is_empty());
2232 assert!(sheet.language.is_empty());
2233 assert!(sheet.relation.is_empty());
2234 assert!(sheet.subject.is_empty());
2235 assert!(sheet.title.is_empty());
2236
2237 assert!(sheet.coverage.is_empty());
2238 assert!(sheet.description.is_empty());
2239 assert!(sheet.format.is_empty());
2240 assert!(sheet.publisher.is_empty());
2241 assert!(sheet.rights.is_empty());
2242 assert!(sheet.source.is_empty());
2243 assert!(sheet.epub_type.is_empty());
2244 }
2245
2246 #[test]
2247 fn test_metadata_sheet_append_vec_fields() {
2248 let mut sheet = MetadataSheet::new();
2249
2250 sheet
2251 .append_title("Test Book")
2252 .append_creator("John Doe")
2253 .append_creator("Jane Smith")
2254 .append_contributor("Editor One")
2255 .append_language("en")
2256 .append_language("zh-CN")
2257 .append_subject("Fiction")
2258 .append_subject("Drama")
2259 .append_relation("prequel");
2260
2261 assert_eq!(sheet.title.len(), 1);
2262 assert_eq!(sheet.title[0], "Test Book");
2263
2264 assert_eq!(sheet.creator.len(), 2);
2265 assert_eq!(sheet.creator[0], "John Doe");
2266 assert_eq!(sheet.creator[1], "Jane Smith");
2267
2268 assert_eq!(sheet.contributor.len(), 1);
2269 assert_eq!(sheet.contributor[0], "Editor One");
2270
2271 assert_eq!(sheet.language.len(), 2);
2272 assert_eq!(sheet.language[0], "en");
2273 assert_eq!(sheet.language[1], "zh-CN");
2274
2275 assert_eq!(sheet.subject.len(), 2);
2276 assert_eq!(sheet.subject[0], "Fiction");
2277 assert_eq!(sheet.subject[1], "Drama");
2278
2279 assert_eq!(sheet.relation.len(), 1);
2280 assert_eq!(sheet.relation[0], "prequel");
2281 }
2282
2283 #[test]
2284 fn test_metadata_sheet_append_date_and_identifier() {
2285 let mut sheet = MetadataSheet::new();
2286
2287 sheet
2288 .append_date("2024-01-15", "publication")
2289 .append_date("2024-01-10", "creation")
2290 .append_identifier("book-id", "urn:isbn:1234567890")
2291 .append_identifier("uuid-id", "urn:uuid:12345678-1234-1234-1234-123456789012");
2292
2293 assert_eq!(sheet.date.len(), 2);
2294 assert_eq!(
2295 sheet.date.get("2024-01-15"),
2296 Some(&"publication".to_string())
2297 );
2298 assert_eq!(sheet.date.get("2024-01-10"), Some(&"creation".to_string()));
2299
2300 assert_eq!(sheet.identifier.len(), 2);
2301 assert_eq!(
2302 sheet.identifier.get("book-id"),
2303 Some(&"urn:isbn:1234567890".to_string())
2304 );
2305 assert_eq!(
2306 sheet.identifier.get("uuid-id"),
2307 Some(&"urn:uuid:12345678-1234-1234-1234-123456789012".to_string())
2308 );
2309 }
2310
2311 #[test]
2312 fn test_metadata_sheet_with_string_fields() {
2313 let mut sheet = MetadataSheet::new();
2314
2315 sheet
2316 .with_coverage("Spatial coverage")
2317 .with_description("A test book description")
2318 .with_format("application/epub+zip")
2319 .with_publisher("Test Publisher")
2320 .with_rights("Copyright 2024")
2321 .with_source("Original source")
2322 .with_epub_type("buku");
2323
2324 assert_eq!(sheet.coverage, "Spatial coverage");
2325 assert_eq!(sheet.description, "A test book description");
2326 assert_eq!(sheet.format, "application/epub+zip");
2327 assert_eq!(sheet.publisher, "Test Publisher");
2328 assert_eq!(sheet.rights, "Copyright 2024");
2329 assert_eq!(sheet.source, "Original source");
2330 assert_eq!(sheet.epub_type, "buku");
2331 }
2332
2333 #[test]
2334 fn test_metadata_sheet_builder_chaining() {
2335 let mut sheet = MetadataSheet::new();
2336
2337 sheet
2338 .append_title("Chained Book")
2339 .append_creator("Chained Author")
2340 .append_date("2024-01-01", "")
2341 .append_identifier("id-1", "test-id")
2342 .with_publisher("Chained Publisher")
2343 .with_description("Chained description");
2344
2345 assert_eq!(sheet.title.len(), 1);
2346 assert_eq!(sheet.title[0], "Chained Book");
2347
2348 assert_eq!(sheet.creator.len(), 1);
2349 assert_eq!(sheet.creator[0], "Chained Author");
2350
2351 assert_eq!(sheet.date.len(), 1);
2352 assert_eq!(sheet.identifier.len(), 1);
2353 assert_eq!(sheet.publisher, "Chained Publisher");
2354 assert_eq!(sheet.description, "Chained description");
2355 }
2356
2357 #[test]
2358 fn test_metadata_sheet_build() {
2359 let mut sheet = MetadataSheet::new();
2360 sheet
2361 .append_title("Original Title")
2362 .with_publisher("Original Publisher");
2363
2364 let built = sheet.build();
2365
2366 assert_eq!(built.title.len(), 1);
2367 assert_eq!(built.title[0], "Original Title");
2368 assert_eq!(built.publisher, "Original Publisher");
2369
2370 sheet.append_title("New Title");
2371 sheet.with_publisher("New Publisher");
2372
2373 assert_eq!(sheet.title.len(), 2);
2374 assert_eq!(built.title.len(), 1);
2375 assert_eq!(built.publisher, "Original Publisher");
2376 }
2377
2378 #[test]
2379 fn test_metadata_sheet_into_metadata_items() {
2380 let mut sheet = MetadataSheet::new();
2381 sheet
2382 .append_title("Test Title")
2383 .append_creator("Test Creator")
2384 .with_description("Test Description")
2385 .with_publisher("Test Publisher");
2386
2387 let items: Vec<MetadataItem> = sheet.into();
2388
2389 assert_eq!(items.len(), 4);
2390
2391 assert!(
2392 items
2393 .iter()
2394 .any(|i| i.property == "title" && i.value == "Test Title")
2395 );
2396
2397 assert!(
2398 items
2399 .iter()
2400 .any(|i| i.property == "creator" && i.value == "Test Creator")
2401 );
2402
2403 assert!(
2404 items
2405 .iter()
2406 .any(|i| i.property == "description" && i.value == "Test Description")
2407 );
2408
2409 assert!(
2410 items
2411 .iter()
2412 .any(|i| i.property == "publisher" && i.value == "Test Publisher")
2413 );
2414 }
2415
2416 #[test]
2417 fn test_metadata_sheet_into_metadata_items_with_date_and_identifier() {
2418 let mut sheet = MetadataSheet::new();
2419 sheet
2420 .append_date("2024-01-15", "publication")
2421 .append_identifier("book-id", "urn:isbn:9876543210");
2422
2423 let items: Vec<MetadataItem> = sheet.into();
2424
2425 assert_eq!(items.len(), 2);
2426
2427 let date_item = items.iter().find(|i| i.property == "date").unwrap();
2428
2429 assert_eq!(date_item.value, "2024-01-15");
2430 assert!(date_item.id.is_some());
2431 assert_eq!(date_item.refined.len(), 1);
2432 assert_eq!(date_item.refined[0].property, "event");
2433 assert_eq!(date_item.refined[0].value, "publication");
2434
2435 let id_item = items.iter().find(|i| i.property == "identifier").unwrap();
2436
2437 assert_eq!(id_item.value, "urn:isbn:9876543210");
2438 assert_eq!(id_item.id, Some("book-id".to_string()));
2439 }
2440
2441 #[test]
2442 fn test_metadata_sheet_into_metadata_items_ignores_empty_fields() {
2443 let mut sheet = MetadataSheet::new();
2444 sheet.append_title("Valid Title").with_description(""); let items: Vec<MetadataItem> = sheet.into();
2447
2448 assert_eq!(items.len(), 1);
2449 assert_eq!(items[0].property, "title");
2450 }
2451 }
2452
2453 mod navpoint {
2454
2455 use std::path::PathBuf;
2456
2457 use crate::types::NavPoint;
2458
2459 #[test]
2460 fn test_navpoint_new() {
2461 let navpoint = NavPoint::new("Test Chapter");
2462
2463 assert_eq!(navpoint.label, "Test Chapter");
2464 assert_eq!(navpoint.content, None);
2465 assert_eq!(navpoint.children.len(), 0);
2466 }
2467
2468 #[test]
2469 fn test_navpoint_with_content() {
2470 let mut navpoint = NavPoint::new("Test Chapter");
2471 navpoint.with_content("chapter1.html");
2472
2473 assert_eq!(navpoint.label, "Test Chapter");
2474 assert_eq!(navpoint.content, Some(PathBuf::from("chapter1.html")));
2475 assert_eq!(navpoint.children.len(), 0);
2476 }
2477
2478 #[test]
2479 fn test_navpoint_append_child() {
2480 let mut parent = NavPoint::new("Parent Chapter");
2481
2482 let mut child1 = NavPoint::new("Child Section 1");
2483 child1.with_content("section1.html");
2484
2485 let mut child2 = NavPoint::new("Child Section 2");
2486 child2.with_content("section2.html");
2487
2488 parent.append_child(child1.build());
2489 parent.append_child(child2.build());
2490
2491 assert_eq!(parent.children.len(), 2);
2492 assert_eq!(parent.children[0].label, "Child Section 1");
2493 assert_eq!(parent.children[1].label, "Child Section 2");
2494 }
2495
2496 #[test]
2497 fn test_navpoint_set_children() {
2498 let mut navpoint = NavPoint::new("Main Chapter");
2499 let children = vec![NavPoint::new("Section 1"), NavPoint::new("Section 2")];
2500
2501 navpoint.set_children(children);
2502
2503 assert_eq!(navpoint.children.len(), 2);
2504 assert_eq!(navpoint.children[0].label, "Section 1");
2505 assert_eq!(navpoint.children[1].label, "Section 2");
2506 }
2507
2508 #[test]
2509 fn test_navpoint_build() {
2510 let mut navpoint = NavPoint::new("Complete Chapter");
2511 navpoint.with_content("complete.html");
2512
2513 let child = NavPoint::new("Sub Section");
2514 navpoint.append_child(child.build());
2515
2516 let built = navpoint.build();
2517
2518 assert_eq!(built.label, "Complete Chapter");
2519 assert_eq!(built.content, Some(PathBuf::from("complete.html")));
2520 assert_eq!(built.children.len(), 1);
2521 assert_eq!(built.children[0].label, "Sub Section");
2522 }
2523
2524 #[test]
2525 fn test_navpoint_builder_chaining() {
2526 let mut navpoint = NavPoint::new("Chained Chapter");
2527
2528 navpoint
2529 .with_content("chained.html")
2530 .append_child(NavPoint::new("Child 1").build())
2531 .append_child(NavPoint::new("Child 2").build());
2532
2533 let built = navpoint.build();
2534
2535 assert_eq!(built.label, "Chained Chapter");
2536 assert_eq!(built.content, Some(PathBuf::from("chained.html")));
2537 assert_eq!(built.children.len(), 2);
2538 }
2539
2540 #[test]
2541 fn test_navpoint_empty_children() {
2542 let navpoint = NavPoint::new("No Children Chapter");
2543 let built = navpoint.build();
2544
2545 assert_eq!(built.children.len(), 0);
2546 }
2547
2548 #[test]
2549 fn test_navpoint_complex_hierarchy() {
2550 let mut root = NavPoint::new("Book");
2551
2552 let mut chapter1 = NavPoint::new("Chapter 1");
2553 chapter1
2554 .with_content("chapter1.html")
2555 .append_child(
2556 NavPoint::new("Section 1.1")
2557 .with_content("sec1_1.html")
2558 .build(),
2559 )
2560 .append_child(
2561 NavPoint::new("Section 1.2")
2562 .with_content("sec1_2.html")
2563 .build(),
2564 );
2565
2566 let mut chapter2 = NavPoint::new("Chapter 2");
2567 chapter2.with_content("chapter2.html").append_child(
2568 NavPoint::new("Section 2.1")
2569 .with_content("sec2_1.html")
2570 .build(),
2571 );
2572
2573 root.append_child(chapter1.build())
2574 .append_child(chapter2.build());
2575
2576 let book = root.build();
2577
2578 assert_eq!(book.label, "Book");
2579 assert_eq!(book.children.len(), 2);
2580
2581 let ch1 = &book.children[0];
2582 assert_eq!(ch1.label, "Chapter 1");
2583 assert_eq!(ch1.children.len(), 2);
2584
2585 let ch2 = &book.children[1];
2586 assert_eq!(ch2.label, "Chapter 2");
2587 assert_eq!(ch2.children.len(), 1);
2588 }
2589 }
2590 }
2591
2592 #[cfg(feature = "content-builder")]
2593 mod footnote_tests {
2594 use crate::types::Footnote;
2595
2596 #[test]
2597 fn test_footnote_basic_creation() {
2598 let footnote = Footnote {
2599 locate: 100,
2600 content: "Sample footnote".to_string(),
2601 };
2602
2603 assert_eq!(footnote.locate, 100);
2604 assert_eq!(footnote.content, "Sample footnote");
2605 }
2606
2607 #[test]
2608 fn test_footnote_equality() {
2609 let footnote1 = Footnote {
2610 locate: 100,
2611 content: "First note".to_string(),
2612 };
2613
2614 let footnote2 = Footnote {
2615 locate: 100,
2616 content: "First note".to_string(),
2617 };
2618
2619 let footnote3 = Footnote {
2620 locate: 100,
2621 content: "Different note".to_string(),
2622 };
2623
2624 let footnote4 = Footnote {
2625 locate: 200,
2626 content: "First note".to_string(),
2627 };
2628
2629 assert_eq!(footnote1, footnote2);
2630 assert_ne!(footnote1, footnote3);
2631 assert_ne!(footnote1, footnote4);
2632 }
2633
2634 #[test]
2635 fn test_footnote_ordering() {
2636 let footnote1 = Footnote {
2637 locate: 100,
2638 content: "First".to_string(),
2639 };
2640
2641 let footnote2 = Footnote {
2642 locate: 200,
2643 content: "Second".to_string(),
2644 };
2645
2646 let footnote3 = Footnote {
2647 locate: 150,
2648 content: "Middle".to_string(),
2649 };
2650
2651 assert!(footnote1 < footnote2);
2652 assert!(footnote2 > footnote1);
2653 assert!(footnote1 < footnote3);
2654 assert!(footnote3 < footnote2);
2655 assert_eq!(footnote1.cmp(&footnote1), std::cmp::Ordering::Equal);
2656 }
2657
2658 #[test]
2659 fn test_footnote_sorting() {
2660 let mut footnotes = vec![
2661 Footnote {
2662 locate: 300,
2663 content: "Third note".to_string(),
2664 },
2665 Footnote {
2666 locate: 100,
2667 content: "First note".to_string(),
2668 },
2669 Footnote {
2670 locate: 200,
2671 content: "Second note".to_string(),
2672 },
2673 ];
2674
2675 footnotes.sort();
2676
2677 assert_eq!(footnotes[0].locate, 100);
2678 assert_eq!(footnotes[1].locate, 200);
2679 assert_eq!(footnotes[2].locate, 300);
2680
2681 assert_eq!(footnotes[0].content, "First note");
2682 assert_eq!(footnotes[1].content, "Second note");
2683 assert_eq!(footnotes[2].content, "Third note");
2684 }
2685 }
2686
2687 #[cfg(feature = "content-builder")]
2688 mod block_type_tests {
2689 use crate::types::BlockType;
2690
2691 #[test]
2692 fn test_block_type_variants() {
2693 let _ = BlockType::Text;
2694 let _ = BlockType::Quote;
2695 let _ = BlockType::Title;
2696 let _ = BlockType::Image;
2697 let _ = BlockType::Audio;
2698 let _ = BlockType::Video;
2699 let _ = BlockType::MathML;
2700 }
2701
2702 #[test]
2703 fn test_block_type_debug() {
2704 let text = format!("{:?}", BlockType::Text);
2705 assert_eq!(text, "Text");
2706
2707 let quote = format!("{:?}", BlockType::Quote);
2708 assert_eq!(quote, "Quote");
2709
2710 let image = format!("{:?}", BlockType::Image);
2711 assert_eq!(image, "Image");
2712 }
2713 }
2714
2715 #[cfg(feature = "content-builder")]
2716 mod style_options_tests {
2717 use crate::types::{ColorScheme, PageLayout, StyleOptions, TextAlign, TextStyle};
2718
2719 #[test]
2720 fn test_style_options_default() {
2721 let options = StyleOptions::default();
2722
2723 assert_eq!(options.text.font_size, 1.0);
2724 assert_eq!(options.text.line_height, 1.6);
2725 assert_eq!(
2726 options.text.font_family,
2727 "-apple-system, Roboto, sans-serif"
2728 );
2729 assert_eq!(options.text.font_weight, "normal");
2730 assert_eq!(options.text.font_style, "normal");
2731 assert_eq!(options.text.letter_spacing, "normal");
2732 assert_eq!(options.text.text_indent, 2.0);
2733
2734 assert_eq!(options.color_scheme.background, "#FFFFFF");
2735 assert_eq!(options.color_scheme.text, "#000000");
2736 assert_eq!(options.color_scheme.link, "#6f6f6f");
2737
2738 assert_eq!(options.layout.margin, 20);
2739 assert_eq!(options.layout.text_align, TextAlign::Left);
2740 assert_eq!(options.layout.paragraph_spacing, 16);
2741 }
2742
2743 #[test]
2744 fn test_style_options_custom_values() {
2745 let text = TextStyle {
2746 font_size: 1.5,
2747 line_height: 2.0,
2748 font_family: "Georgia, serif".to_string(),
2749 font_weight: "bold".to_string(),
2750 font_style: "italic".to_string(),
2751 letter_spacing: "0.1em".to_string(),
2752 text_indent: 3.0,
2753 };
2754
2755 let color_scheme = ColorScheme {
2756 background: "#F0F0F0".to_string(),
2757 text: "#333333".to_string(),
2758 link: "#0066CC".to_string(),
2759 };
2760
2761 let layout = PageLayout {
2762 margin: 30,
2763 text_align: TextAlign::Center,
2764 paragraph_spacing: 20,
2765 };
2766
2767 let options = StyleOptions { text, color_scheme, layout };
2768
2769 assert_eq!(options.text.font_size, 1.5);
2770 assert_eq!(options.text.font_weight, "bold");
2771 assert_eq!(options.color_scheme.background, "#F0F0F0");
2772 assert_eq!(options.layout.text_align, TextAlign::Center);
2773 }
2774
2775 #[test]
2776 fn test_text_style_default() {
2777 let style = TextStyle::default();
2778
2779 assert_eq!(style.font_size, 1.0);
2780 assert_eq!(style.line_height, 1.6);
2781 assert_eq!(style.font_family, "-apple-system, Roboto, sans-serif");
2782 assert_eq!(style.font_weight, "normal");
2783 assert_eq!(style.font_style, "normal");
2784 assert_eq!(style.letter_spacing, "normal");
2785 assert_eq!(style.text_indent, 2.0);
2786 }
2787
2788 #[test]
2789 fn test_text_style_custom_values() {
2790 let style = TextStyle {
2791 font_size: 2.0,
2792 line_height: 1.8,
2793 font_family: "Times New Roman".to_string(),
2794 font_weight: "bold".to_string(),
2795 font_style: "italic".to_string(),
2796 letter_spacing: "0.05em".to_string(),
2797 text_indent: 0.0,
2798 };
2799
2800 assert_eq!(style.font_size, 2.0);
2801 assert_eq!(style.line_height, 1.8);
2802 assert_eq!(style.font_family, "Times New Roman");
2803 assert_eq!(style.font_weight, "bold");
2804 assert_eq!(style.font_style, "italic");
2805 assert_eq!(style.letter_spacing, "0.05em");
2806 assert_eq!(style.text_indent, 0.0);
2807 }
2808
2809 #[test]
2810 fn test_text_style_debug() {
2811 let style = TextStyle::default();
2812 let debug_str = format!("{:?}", style);
2813 assert!(debug_str.contains("TextStyle"));
2814 assert!(debug_str.contains("font_size"));
2815 }
2816
2817 #[test]
2818 fn test_color_scheme_default() {
2819 let scheme = ColorScheme::default();
2820
2821 assert_eq!(scheme.background, "#FFFFFF");
2822 assert_eq!(scheme.text, "#000000");
2823 assert_eq!(scheme.link, "#6f6f6f");
2824 }
2825
2826 #[test]
2827 fn test_color_scheme_custom_values() {
2828 let scheme = ColorScheme {
2829 background: "#000000".to_string(),
2830 text: "#FFFFFF".to_string(),
2831 link: "#00FF00".to_string(),
2832 };
2833
2834 assert_eq!(scheme.background, "#000000");
2835 assert_eq!(scheme.text, "#FFFFFF");
2836 assert_eq!(scheme.link, "#00FF00");
2837 }
2838
2839 #[test]
2840 fn test_color_scheme_debug() {
2841 let scheme = ColorScheme::default();
2842 let debug_str = format!("{:?}", scheme);
2843 assert!(debug_str.contains("ColorScheme"));
2844 assert!(debug_str.contains("background"));
2845 }
2846
2847 #[test]
2848 fn test_page_layout_default() {
2849 let layout = PageLayout::default();
2850
2851 assert_eq!(layout.margin, 20);
2852 assert_eq!(layout.text_align, TextAlign::Left);
2853 assert_eq!(layout.paragraph_spacing, 16);
2854 }
2855
2856 #[test]
2857 fn test_page_layout_custom_values() {
2858 let layout = PageLayout {
2859 margin: 40,
2860 text_align: TextAlign::Justify,
2861 paragraph_spacing: 24,
2862 };
2863
2864 assert_eq!(layout.margin, 40);
2865 assert_eq!(layout.text_align, TextAlign::Justify);
2866 assert_eq!(layout.paragraph_spacing, 24);
2867 }
2868
2869 #[test]
2870 fn test_page_layout_debug() {
2871 let layout = PageLayout::default();
2872 let debug_str = format!("{:?}", layout);
2873 assert!(debug_str.contains("PageLayout"));
2874 assert!(debug_str.contains("margin"));
2875 }
2876
2877 #[test]
2878 fn test_text_align_default() {
2879 let align = TextAlign::default();
2880 assert_eq!(align, TextAlign::Left);
2881 }
2882
2883 #[test]
2884 fn test_text_align_display() {
2885 assert_eq!(TextAlign::Left.to_string(), "left");
2886 assert_eq!(TextAlign::Right.to_string(), "right");
2887 assert_eq!(TextAlign::Justify.to_string(), "justify");
2888 assert_eq!(TextAlign::Center.to_string(), "center");
2889 }
2890
2891 #[test]
2892 fn test_text_align_all_variants() {
2893 let left = TextAlign::Left;
2894 let right = TextAlign::Right;
2895 let justify = TextAlign::Justify;
2896 let center = TextAlign::Center;
2897
2898 assert!(matches!(left, TextAlign::Left));
2899 assert!(matches!(right, TextAlign::Right));
2900 assert!(matches!(justify, TextAlign::Justify));
2901 assert!(matches!(center, TextAlign::Center));
2902 }
2903
2904 #[test]
2905 fn test_text_align_debug() {
2906 assert_eq!(format!("{:?}", TextAlign::Left), "Left");
2907 assert_eq!(format!("{:?}", TextAlign::Right), "Right");
2908 assert_eq!(format!("{:?}", TextAlign::Justify), "Justify");
2909 assert_eq!(format!("{:?}", TextAlign::Center), "Center");
2910 }
2911
2912 #[test]
2913 fn test_style_options_builder_new() {
2914 let options = StyleOptions::new();
2915 assert_eq!(options.text.font_size, 1.0);
2916 assert_eq!(options.color_scheme.background, "#FFFFFF");
2917 assert_eq!(options.layout.margin, 20);
2918 }
2919
2920 #[test]
2921 fn test_style_options_builder_with_text() {
2922 let mut options = StyleOptions::new();
2923 let text_style = TextStyle::new()
2924 .with_font_size(2.0)
2925 .with_font_weight("bold")
2926 .build();
2927 options.with_text(text_style);
2928
2929 assert_eq!(options.text.font_size, 2.0);
2930 assert_eq!(options.text.font_weight, "bold");
2931 }
2932
2933 #[test]
2934 fn test_style_options_builder_with_color_scheme() {
2935 let mut options = StyleOptions::new();
2936 let color = ColorScheme::new()
2937 .with_background("#000000")
2938 .with_text("#FFFFFF")
2939 .build();
2940 options.with_color_scheme(color);
2941
2942 assert_eq!(options.color_scheme.background, "#000000");
2943 assert_eq!(options.color_scheme.text, "#FFFFFF");
2944 }
2945
2946 #[test]
2947 fn test_style_options_builder_with_layout() {
2948 let mut options = StyleOptions::new();
2949 let layout = PageLayout::new()
2950 .with_margin(40)
2951 .with_text_align(TextAlign::Justify)
2952 .with_paragraph_spacing(24)
2953 .build();
2954 options.with_layout(layout);
2955
2956 assert_eq!(options.layout.margin, 40);
2957 assert_eq!(options.layout.text_align, TextAlign::Justify);
2958 assert_eq!(options.layout.paragraph_spacing, 24);
2959 }
2960
2961 #[test]
2962 fn test_style_options_builder_build() {
2963 let options = StyleOptions::new()
2964 .with_text(TextStyle::new().with_font_size(1.5).build())
2965 .with_color_scheme(ColorScheme::new().with_link("#FF0000").build())
2966 .with_layout(PageLayout::new().with_margin(30).build())
2967 .build();
2968
2969 assert_eq!(options.text.font_size, 1.5);
2970 assert_eq!(options.color_scheme.link, "#FF0000");
2971 assert_eq!(options.layout.margin, 30);
2972 }
2973
2974 #[test]
2975 fn test_style_options_builder_chaining() {
2976 let options = StyleOptions::new()
2977 .with_text(
2978 TextStyle::new()
2979 .with_font_size(1.5)
2980 .with_line_height(2.0)
2981 .with_font_family("Arial")
2982 .with_font_weight("bold")
2983 .with_font_style("italic")
2984 .with_letter_spacing("0.1em")
2985 .with_text_indent(1.5)
2986 .build(),
2987 )
2988 .with_color_scheme(
2989 ColorScheme::new()
2990 .with_background("#CCCCCC")
2991 .with_text("#111111")
2992 .with_link("#0000FF")
2993 .build(),
2994 )
2995 .with_layout(
2996 PageLayout::new()
2997 .with_margin(25)
2998 .with_text_align(TextAlign::Right)
2999 .with_paragraph_spacing(20)
3000 .build(),
3001 )
3002 .build();
3003
3004 assert_eq!(options.text.font_size, 1.5);
3005 assert_eq!(options.text.line_height, 2.0);
3006 assert_eq!(options.text.font_family, "Arial");
3007 assert_eq!(options.text.font_weight, "bold");
3008 assert_eq!(options.text.font_style, "italic");
3009 assert_eq!(options.text.letter_spacing, "0.1em");
3010 assert_eq!(options.text.text_indent, 1.5);
3011
3012 assert_eq!(options.color_scheme.background, "#CCCCCC");
3013 assert_eq!(options.color_scheme.text, "#111111");
3014 assert_eq!(options.color_scheme.link, "#0000FF");
3015
3016 assert_eq!(options.layout.margin, 25);
3017 assert_eq!(options.layout.text_align, TextAlign::Right);
3018 assert_eq!(options.layout.paragraph_spacing, 20);
3019 }
3020
3021 #[test]
3022 fn test_text_style_builder_new() {
3023 let style = TextStyle::new();
3024 assert_eq!(style.font_size, 1.0);
3025 assert_eq!(style.line_height, 1.6);
3026 }
3027
3028 #[test]
3029 fn test_text_style_builder_with_font_size() {
3030 let mut style = TextStyle::new();
3031 style.with_font_size(2.5);
3032 assert_eq!(style.font_size, 2.5);
3033 }
3034
3035 #[test]
3036 fn test_text_style_builder_with_line_height() {
3037 let mut style = TextStyle::new();
3038 style.with_line_height(2.0);
3039 assert_eq!(style.line_height, 2.0);
3040 }
3041
3042 #[test]
3043 fn test_text_style_builder_with_font_family() {
3044 let mut style = TextStyle::new();
3045 style.with_font_family("Helvetica, Arial");
3046 assert_eq!(style.font_family, "Helvetica, Arial");
3047 }
3048
3049 #[test]
3050 fn test_text_style_builder_with_font_weight() {
3051 let mut style = TextStyle::new();
3052 style.with_font_weight("bold");
3053 assert_eq!(style.font_weight, "bold");
3054 }
3055
3056 #[test]
3057 fn test_text_style_builder_with_font_style() {
3058 let mut style = TextStyle::new();
3059 style.with_font_style("italic");
3060 assert_eq!(style.font_style, "italic");
3061 }
3062
3063 #[test]
3064 fn test_text_style_builder_with_letter_spacing() {
3065 let mut style = TextStyle::new();
3066 style.with_letter_spacing("0.05em");
3067 assert_eq!(style.letter_spacing, "0.05em");
3068 }
3069
3070 #[test]
3071 fn test_text_style_builder_with_text_indent() {
3072 let mut style = TextStyle::new();
3073 style.with_text_indent(3.0);
3074 assert_eq!(style.text_indent, 3.0);
3075 }
3076
3077 #[test]
3078 fn test_text_style_builder_build() {
3079 let style = TextStyle::new()
3080 .with_font_size(1.8)
3081 .with_line_height(1.9)
3082 .build();
3083
3084 assert_eq!(style.font_size, 1.8);
3085 assert_eq!(style.line_height, 1.9);
3086 }
3087
3088 #[test]
3089 fn test_text_style_builder_chaining() {
3090 let style = TextStyle::new()
3091 .with_font_size(2.0)
3092 .with_line_height(1.8)
3093 .with_font_family("Georgia")
3094 .with_font_weight("bold")
3095 .with_font_style("italic")
3096 .with_letter_spacing("0.1em")
3097 .with_text_indent(0.5)
3098 .build();
3099
3100 assert_eq!(style.font_size, 2.0);
3101 assert_eq!(style.line_height, 1.8);
3102 assert_eq!(style.font_family, "Georgia");
3103 assert_eq!(style.font_weight, "bold");
3104 assert_eq!(style.font_style, "italic");
3105 assert_eq!(style.letter_spacing, "0.1em");
3106 assert_eq!(style.text_indent, 0.5);
3107 }
3108
3109 #[test]
3110 fn test_color_scheme_builder_new() {
3111 let scheme = ColorScheme::new();
3112 assert_eq!(scheme.background, "#FFFFFF");
3113 assert_eq!(scheme.text, "#000000");
3114 }
3115
3116 #[test]
3117 fn test_color_scheme_builder_with_background() {
3118 let mut scheme = ColorScheme::new();
3119 scheme.with_background("#FF0000");
3120 assert_eq!(scheme.background, "#FF0000");
3121 }
3122
3123 #[test]
3124 fn test_color_scheme_builder_with_text() {
3125 let mut scheme = ColorScheme::new();
3126 scheme.with_text("#333333");
3127 assert_eq!(scheme.text, "#333333");
3128 }
3129
3130 #[test]
3131 fn test_color_scheme_builder_with_link() {
3132 let mut scheme = ColorScheme::new();
3133 scheme.with_link("#0000FF");
3134 assert_eq!(scheme.link, "#0000FF");
3135 }
3136
3137 #[test]
3138 fn test_color_scheme_builder_build() {
3139 let scheme = ColorScheme::new().with_background("#123456").build();
3140
3141 assert_eq!(scheme.background, "#123456");
3142 assert_eq!(scheme.text, "#000000");
3143 }
3144
3145 #[test]
3146 fn test_color_scheme_builder_chaining() {
3147 let scheme = ColorScheme::new()
3148 .with_background("#AABBCC")
3149 .with_text("#DDEEFF")
3150 .with_link("#112233")
3151 .build();
3152
3153 assert_eq!(scheme.background, "#AABBCC");
3154 assert_eq!(scheme.text, "#DDEEFF");
3155 assert_eq!(scheme.link, "#112233");
3156 }
3157
3158 #[test]
3159 fn test_page_layout_builder_new() {
3160 let layout = PageLayout::new();
3161 assert_eq!(layout.margin, 20);
3162 assert_eq!(layout.text_align, TextAlign::Left);
3163 assert_eq!(layout.paragraph_spacing, 16);
3164 }
3165
3166 #[test]
3167 fn test_page_layout_builder_with_margin() {
3168 let mut layout = PageLayout::new();
3169 layout.with_margin(50);
3170 assert_eq!(layout.margin, 50);
3171 }
3172
3173 #[test]
3174 fn test_page_layout_builder_with_text_align() {
3175 let mut layout = PageLayout::new();
3176 layout.with_text_align(TextAlign::Center);
3177 assert_eq!(layout.text_align, TextAlign::Center);
3178 }
3179
3180 #[test]
3181 fn test_page_layout_builder_with_paragraph_spacing() {
3182 let mut layout = PageLayout::new();
3183 layout.with_paragraph_spacing(30);
3184 assert_eq!(layout.paragraph_spacing, 30);
3185 }
3186
3187 #[test]
3188 fn test_page_layout_builder_build() {
3189 let layout = PageLayout::new().with_margin(35).build();
3190
3191 assert_eq!(layout.margin, 35);
3192 assert_eq!(layout.text_align, TextAlign::Left);
3193 }
3194
3195 #[test]
3196 fn test_page_layout_builder_chaining() {
3197 let layout = PageLayout::new()
3198 .with_margin(45)
3199 .with_text_align(TextAlign::Justify)
3200 .with_paragraph_spacing(28)
3201 .build();
3202
3203 assert_eq!(layout.margin, 45);
3204 assert_eq!(layout.text_align, TextAlign::Justify);
3205 assert_eq!(layout.paragraph_spacing, 28);
3206 }
3207
3208 #[test]
3209 fn test_page_layout_builder_all_text_align_variants() {
3210 let left = PageLayout::new().with_text_align(TextAlign::Left).build();
3211 assert_eq!(left.text_align, TextAlign::Left);
3212
3213 let right = PageLayout::new().with_text_align(TextAlign::Right).build();
3214 assert_eq!(right.text_align, TextAlign::Right);
3215
3216 let center = PageLayout::new().with_text_align(TextAlign::Center).build();
3217 assert_eq!(center.text_align, TextAlign::Center);
3218
3219 let justify = PageLayout::new()
3220 .with_text_align(TextAlign::Justify)
3221 .build();
3222 assert_eq!(justify.text_align, TextAlign::Justify);
3223 }
3224 }
3225}