1use std::cell::RefCell;
22use std::collections::HashMap;
23use std::rc::Rc;
24
25use crate::value::{Color, Length, Measure, Percent};
26use crate::xml::{Element, Ns};
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34pub enum Family {
35 Paragraph,
37 Text,
39 Section,
41 Table,
43 TableColumn,
45 TableRow,
47 TableCell,
49 Graphic,
51 Presentation,
53 DrawingPage,
55 Chart,
57 Ruby,
59 Other(Box<str>),
62}
63
64impl Family {
65 pub fn parse(text: &str) -> Self {
67 match text {
68 "paragraph" => Self::Paragraph,
69 "text" => Self::Text,
70 "section" => Self::Section,
71 "table" => Self::Table,
72 "table-column" => Self::TableColumn,
73 "table-row" => Self::TableRow,
74 "table-cell" => Self::TableCell,
75 "graphic" => Self::Graphic,
76 "presentation" => Self::Presentation,
77 "drawing-page" => Self::DrawingPage,
78 "chart" => Self::Chart,
79 "ruby" => Self::Ruby,
80 other => Self::Other(other.into()),
81 }
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum TextAlign {
88 Start,
90 End,
92 Center,
94 Justify,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum Position {
101 Baseline,
103 Super,
105 Sub,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum VerticalAlign {
112 Top,
114 Middle,
116 Bottom,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum Break {
123 Auto,
125 Page,
127 Column,
129}
130
131#[derive(Debug, Clone, Copy, PartialEq)]
133pub struct Border {
134 pub width: Length,
136 pub color: Color,
138}
139
140impl Border {
141 fn parse(text: &str) -> Option<Self> {
147 let mut width = None;
148 let mut color = None;
149 for word in text.split_whitespace() {
150 match word {
151 "none" | "hidden" => return None,
152 _ => {
153 if let Some(length) = Length::parse(word) {
154 width = Some(length);
155 } else if let Some(parsed) = Color::parse(word) {
156 color = Some(parsed);
157 }
158 }
159 }
160 }
161 Some(Self {
162 width: width.unwrap_or(Length(0.5)),
165 color: color.unwrap_or(Color { r: 0, g: 0, b: 0 }),
166 })
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq)]
172pub struct Edges<T> {
173 pub left: Option<T>,
175 pub right: Option<T>,
177 pub top: Option<T>,
179 pub bottom: Option<T>,
181}
182
183impl<T> Default for Edges<T> {
188 fn default() -> Self {
189 Self {
190 left: None,
191 right: None,
192 top: None,
193 bottom: None,
194 }
195 }
196}
197
198#[derive(Debug, Clone, Default, PartialEq)]
200pub struct TextProperties {
201 pub font_family: Option<String>,
204 pub size: Option<Measure>,
206 pub bold: Option<bool>,
208 pub italic: Option<bool>,
211 pub underline: Option<bool>,
213 pub strike: Option<bool>,
215 pub color: Option<Color>,
217 pub background: Option<Color>,
219 pub position: Option<Position>,
221 pub uppercase: Option<bool>,
223}
224
225#[derive(Debug, Clone, Default, PartialEq)]
227pub struct ParagraphProperties {
228 pub align: Option<TextAlign>,
230 pub margin: Edges<Length>,
232 pub text_indent: Option<Length>,
234 pub line_height: Option<Measure>,
236 pub background: Option<Color>,
238 pub break_before: Option<Break>,
240 pub break_after: Option<Break>,
242 pub border: Edges<Border>,
244 pub padding: Edges<Length>,
246}
247
248#[derive(Debug, Clone, Default, PartialEq)]
250pub struct CellProperties {
251 pub background: Option<Color>,
253 pub vertical_align: Option<VerticalAlign>,
255 pub border: Edges<Border>,
257 pub padding: Edges<Length>,
259 pub wrap: Option<bool>,
261}
262
263#[derive(Debug, Clone, Default, PartialEq)]
270pub enum Fill {
271 #[default]
273 None,
274 Solid(Color),
276 Gradient(String),
278 Image(String),
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub enum GradientStyle {
290 Linear,
292 Axial,
294 Radial,
296 Ellipsoidal,
298 Square,
300 Rectangular,
302}
303
304#[derive(Debug, Clone, PartialEq)]
313pub struct Gradient {
314 pub style: GradientStyle,
316 pub start: Color,
318 pub end: Color,
320 pub angle: f32,
323 pub border: f32,
326 pub center: (f32, f32),
329}
330
331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333enum FillKind {
334 None,
335 Solid,
336 Gradient,
337 Bitmap,
339 Hatch,
341}
342
343#[derive(Debug, Clone, Default, PartialEq)]
353pub struct GraphicProperties {
354 kind: Option<FillKind>,
355 color: Option<Color>,
356 gradient: Option<String>,
357 image: Option<String>,
358 pub stroke: Option<Color>,
360 pub stroke_width: Option<Length>,
362 pub opacity: Option<f32>,
364}
365
366impl GraphicProperties {
367 pub fn fill(&self) -> Fill {
374 match self.kind {
375 None | Some(FillKind::None | FillKind::Hatch) => Fill::None,
376 Some(FillKind::Solid) => self.color.map_or(Fill::None, Fill::Solid),
377 Some(FillKind::Gradient) => self.gradient.clone().map_or(Fill::None, Fill::Gradient),
378 Some(FillKind::Bitmap) => self.image.clone().map_or(Fill::None, Fill::Image),
379 }
380 }
381}
382
383#[derive(Debug, Clone, Default, PartialEq)]
390pub struct Properties {
391 pub text: TextProperties,
393 pub paragraph: ParagraphProperties,
395 pub cell: CellProperties,
397 pub graphic: GraphicProperties,
399 pub background_visible: Option<bool>,
402 pub background_objects_visible: Option<bool>,
404 pub column_width: Option<Length>,
406 pub row_height: Option<Length>,
408}
409
410#[derive(Debug, Clone, PartialEq)]
412pub struct PageLayout {
413 pub width: Length,
415 pub height: Length,
417 pub margin: Edges<Length>,
419}
420
421impl Default for PageLayout {
422 fn default() -> Self {
431 Self {
432 width: Length(612.0),
433 height: Length(792.0),
434 margin: Edges {
435 left: Some(Length(72.0)),
436 right: Some(Length(72.0)),
437 top: Some(Length(72.0)),
438 bottom: Some(Length(72.0)),
439 },
440 }
441 }
442}
443
444pub struct Style {
446 pub name: String,
448 pub display_name: Option<String>,
452 pub family: Family,
454 pub parent: Option<String>,
456 pub element: Element,
459}
460
461pub struct Styles {
463 by_name: HashMap<(Family, String), Style>,
464 defaults: HashMap<Family, Element>,
465 page_layouts: HashMap<String, PageLayout>,
466 master_pages: HashMap<String, Element>,
467 lists: HashMap<String, Element>,
468 gradients: HashMap<String, Gradient>,
469 fill_images: HashMap<String, String>,
470 font_faces: HashMap<String, String>,
471 cache: RefCell<HashMap<(Family, String), Rc<Properties>>>,
472}
473
474impl Styles {
475 pub fn collect(content: Option<&Element>, styles: Option<&Element>) -> Self {
481 let mut this = Self {
482 by_name: HashMap::new(),
483 defaults: HashMap::new(),
484 page_layouts: HashMap::new(),
485 master_pages: HashMap::new(),
486 lists: HashMap::new(),
487 gradients: HashMap::new(),
488 fill_images: HashMap::new(),
489 font_faces: HashMap::new(),
490 cache: RefCell::new(HashMap::new()),
491 };
492 for root in [styles, content].into_iter().flatten() {
496 this.collect_from(root);
497 }
498 this
499 }
500
501 fn collect_from(&mut self, root: &Element) {
502 for container in root.elements() {
503 match () {
504 () if container.is(&Ns::Office, "font-face-decls") => {
505 for face in container.elements() {
506 let Some(name) = face.attr(&Ns::Style, "name") else {
507 continue;
508 };
509 let family = face
514 .attr(&Ns::Svg, "font-family")
515 .unwrap_or(name)
516 .trim_matches('\'')
517 .to_owned();
518 self.font_faces.insert(name.to_owned(), family);
519 }
520 }
521 () if container.is(&Ns::Office, "styles")
522 || container.is(&Ns::Office, "automatic-styles") =>
523 {
524 for element in container.elements() {
525 self.collect_style(element);
526 }
527 }
528 () if container.is(&Ns::Office, "master-styles") => {
529 for master in container.elements() {
530 if let Some(name) = master.attr(&Ns::Style, "name") {
531 self.master_pages.insert(name.to_owned(), master.clone());
532 }
533 }
534 }
535 () => {}
536 }
537 }
538 }
539
540 fn collect_style(&mut self, element: &Element) {
541 if element.is(&Ns::Style, "style") {
542 let Some(name) = element.attr(&Ns::Style, "name") else {
543 return;
544 };
545 let family = Family::parse(element.attr(&Ns::Style, "family").unwrap_or_default());
546 let style = Style {
547 name: name.to_owned(),
548 display_name: element
549 .attr(&Ns::Style, "display-name")
550 .map(ToOwned::to_owned),
551 family: family.clone(),
552 parent: element
553 .attr(&Ns::Style, "parent-style-name")
554 .map(ToOwned::to_owned),
555 element: element.clone(),
556 };
557 self.by_name.insert((family, name.to_owned()), style);
558 } else if element.is(&Ns::Style, "default-style") {
559 let family = Family::parse(element.attr(&Ns::Style, "family").unwrap_or_default());
560 self.defaults.insert(family, element.clone());
561 } else if element.is(&Ns::Style, "page-layout") {
562 if let Some(name) = element.attr(&Ns::Style, "name") {
563 self.page_layouts
564 .insert(name.to_owned(), page_layout(element));
565 }
566 } else if element.is(&Ns::Draw, "fill-image") {
567 if let (Some(name), Some(href)) = (
568 element.attr(&Ns::Draw, "name"),
569 element.attr(&Ns::Xlink, "href"),
570 ) {
571 self.fill_images.insert(name.to_owned(), href.to_owned());
572 }
573 } else if element.is(&Ns::Draw, "gradient") {
574 if let Some(name) = element.attr(&Ns::Draw, "name") {
575 self.gradients.insert(name.to_owned(), gradient(element));
576 }
577 } else if element.is(&Ns::Text, "list-style")
578 && let Some(name) = element.attr(&Ns::Style, "name")
579 {
580 self.lists.insert(name.to_owned(), element.clone());
581 }
582 }
583
584 pub fn style(&self, family: &Family, name: &str) -> Option<&Style> {
586 self.by_name.get(&(family.clone(), name.to_owned()))
587 }
588
589 pub fn gradient(&self, name: &str) -> Option<&Gradient> {
591 self.gradients.get(name)
592 }
593
594 pub fn fill_image(&self, name: &str) -> Option<&str> {
596 self.fill_images.get(name).map(String::as_str)
597 }
598
599 pub fn list_style(&self, name: &str) -> Option<&Element> {
602 self.lists.get(name)
603 }
604
605 pub fn master_page(&self, name: &str) -> Option<&Element> {
607 self.master_pages.get(name)
608 }
609
610 pub fn page_layout_of(&self, master_page: &str) -> Option<&PageLayout> {
612 let master = self.master_pages.get(master_page)?;
613 let layout = master.attr(&Ns::Style, "page-layout-name")?;
614 self.page_layouts.get(layout)
615 }
616
617 pub fn resolve(&self, family: &Family, name: &str) -> Rc<Properties> {
624 let key = (family.clone(), name.to_owned());
625 if let Some(cached) = self.cache.borrow().get(&key) {
626 return Rc::clone(cached);
627 }
628
629 let mut properties = Properties::default();
630 if let Some(default) = self.defaults.get(family) {
631 properties.apply(default, &self.font_faces);
632 }
633 for style in self.chain(family, name).into_iter().rev() {
635 properties.apply(&style.element, &self.font_faces);
636 }
637
638 let properties = Rc::new(properties);
639 self.cache.borrow_mut().insert(key, Rc::clone(&properties));
640 properties
641 }
642
643 fn chain(&self, family: &Family, name: &str) -> Vec<&Style> {
648 let mut chain: Vec<&Style> = Vec::new();
649 let mut next: Option<&str> = Some(name);
650 while let Some(current) = next {
651 if chain.iter().any(|s| s.name == current) {
652 break;
653 }
654 let Some(style) = self.style(family, current) else {
655 break;
656 };
657 next = style.parent.as_deref();
658 chain.push(style);
659 }
660 chain
661 }
662
663 pub fn font_family(&self, declared: &str) -> String {
667 self.font_faces
668 .get(declared)
669 .cloned()
670 .unwrap_or_else(|| declared.to_owned())
671 }
672}
673
674fn gradient(element: &Element) -> Gradient {
675 let color = |local: &str, fallback: Color| {
676 element
677 .attr(&Ns::Draw, local)
678 .and_then(Color::parse)
679 .unwrap_or(fallback)
680 };
681 let proportion = |local: &str| {
682 element
683 .attr(&Ns::Draw, local)
684 .and_then(Percent::parse)
685 .map_or(0.0, Percent::fraction)
686 };
687 Gradient {
688 style: match element.attr(&Ns::Draw, "style") {
689 Some("axial") => GradientStyle::Axial,
690 Some("radial") => GradientStyle::Radial,
691 Some("ellipsoid") => GradientStyle::Ellipsoidal,
692 Some("square") => GradientStyle::Square,
693 Some("rectangular") => GradientStyle::Rectangular,
694 _ => GradientStyle::Linear,
695 },
696 start: color("start-color", Color { r: 0, g: 0, b: 0 }),
697 end: color(
698 "end-color",
699 Color {
700 r: 0xff,
701 g: 0xff,
702 b: 0xff,
703 },
704 ),
705 angle: element.attr(&Ns::Draw, "angle").map_or(0.0, parse_angle),
708 border: proportion("border"),
709 center: (proportion("cx"), proportion("cy")),
710 }
711}
712
713fn parse_angle(text: &str) -> f32 {
719 let text = text.trim();
720 match text.strip_suffix("deg") {
721 Some(degrees) => degrees.trim().parse().unwrap_or(0.0),
722 None => text.parse::<f32>().unwrap_or(0.0) / 10.0,
723 }
724}
725
726fn page_layout(element: &Element) -> PageLayout {
727 let mut layout = PageLayout::default();
728 if let Some(properties) = element.child(&Ns::Style, "page-layout-properties") {
729 if let Some(width) = properties
730 .attr(&Ns::Fo, "page-width")
731 .and_then(Length::parse)
732 {
733 layout.width = width;
734 }
735 if let Some(height) = properties
736 .attr(&Ns::Fo, "page-height")
737 .and_then(Length::parse)
738 {
739 layout.height = height;
740 }
741 let mut margin = Edges::default();
742 read_edges(properties, "margin", &mut margin, Length::parse);
743 layout.margin.left = margin.left.or(layout.margin.left);
747 layout.margin.right = margin.right.or(layout.margin.right);
748 layout.margin.top = margin.top.or(layout.margin.top);
749 layout.margin.bottom = margin.bottom.or(layout.margin.bottom);
750 }
751 layout
752}
753
754fn read_edges<T: Copy>(
757 properties: &Element,
758 base: &str,
759 into: &mut Edges<T>,
760 parse: impl Fn(&str) -> Option<T>,
761) {
762 if let Some(all) = properties.attr(&Ns::Fo, base).and_then(&parse) {
763 *into = Edges {
764 left: Some(all),
765 right: Some(all),
766 top: Some(all),
767 bottom: Some(all),
768 };
769 }
770 if let Some(v) = properties
771 .attr(&Ns::Fo, &format!("{base}-left"))
772 .and_then(&parse)
773 {
774 into.left = Some(v);
775 }
776 if let Some(v) = properties
777 .attr(&Ns::Fo, &format!("{base}-right"))
778 .and_then(&parse)
779 {
780 into.right = Some(v);
781 }
782 if let Some(v) = properties
783 .attr(&Ns::Fo, &format!("{base}-top"))
784 .and_then(&parse)
785 {
786 into.top = Some(v);
787 }
788 if let Some(v) = properties
789 .attr(&Ns::Fo, &format!("{base}-bottom"))
790 .and_then(&parse)
791 {
792 into.bottom = Some(v);
793 }
794}
795
796impl Properties {
797 fn apply(&mut self, style: &Element, font_faces: &HashMap<String, String>) {
803 for properties in style.elements() {
804 if properties.is(&Ns::Style, "text-properties") {
805 self.text.apply(properties, font_faces);
806 } else if properties.is(&Ns::Style, "paragraph-properties") {
807 self.paragraph.apply(properties);
808 } else if properties.is(&Ns::Style, "table-cell-properties") {
809 self.cell.apply(properties);
810 } else if properties.is(&Ns::Style, "graphic-properties")
811 || properties.is(&Ns::Style, "drawing-page-properties")
812 {
813 self.graphic.apply(properties);
814 if let Some(visible) = properties
818 .attr(&Ns::Presentation, "background-visible")
819 .and_then(crate::value::boolean)
820 {
821 self.background_visible = Some(visible);
822 }
823 if let Some(visible) = properties
824 .attr(&Ns::Presentation, "background-objects-visible")
825 .and_then(crate::value::boolean)
826 {
827 self.background_objects_visible = Some(visible);
828 }
829 } else if properties.is(&Ns::Style, "table-column-properties") {
830 if let Some(width) = properties
831 .attr(&Ns::Style, "column-width")
832 .and_then(Length::parse)
833 {
834 self.column_width = Some(width);
835 }
836 } else if properties.is(&Ns::Style, "table-row-properties")
837 && let Some(height) = properties
838 .attr(&Ns::Style, "row-height")
839 .and_then(Length::parse)
840 {
841 self.row_height = Some(height);
842 }
843 }
844 }
845}
846
847impl TextProperties {
848 fn apply(&mut self, p: &Element, font_faces: &HashMap<String, String>) {
849 if let Some(name) = p.attr(&Ns::Fo, "font-family") {
853 self.font_family = Some(name.trim_matches('\'').to_owned());
854 }
855 if let Some(name) = p.attr(&Ns::Style, "font-name") {
856 self.font_family = Some(
857 font_faces
858 .get(name)
859 .cloned()
860 .unwrap_or_else(|| name.to_owned()),
861 );
862 }
863 if let Some(size) = p.attr(&Ns::Fo, "font-size").and_then(Measure::parse) {
864 self.size = Some(size);
865 }
866 if let Some(weight) = p.attr(&Ns::Fo, "font-weight") {
867 self.bold = Some(match weight {
870 "normal" => false,
871 "bold" => true,
872 other => other.parse::<u32>().is_ok_and(|n| n >= 600),
873 });
874 }
875 if let Some(style) = p.attr(&Ns::Fo, "font-style") {
876 self.italic = Some(style != "normal");
877 }
878 if let Some(line) = p.attr(&Ns::Style, "text-underline-style") {
879 self.underline = Some(line != "none");
880 }
881 if let Some(line) = p.attr(&Ns::Style, "text-line-through-style") {
882 self.strike = Some(line != "none");
883 }
884 if let Some(color) = p.attr(&Ns::Fo, "color") {
885 self.color = Color::parse(color);
886 }
887 if let Some(color) = p.attr(&Ns::Fo, "background-color") {
888 self.background = Color::parse(color);
889 }
890 if let Some(position) = p.attr(&Ns::Style, "text-position") {
891 let first = position.split_whitespace().next().unwrap_or_default();
897 self.position = Some(match first {
898 "super" => Position::Super,
899 "sub" => Position::Sub,
900 _ => match Percent::parse(first) {
901 Some(percent) if percent.0 > 0.0 => Position::Super,
902 Some(percent) if percent.0 < 0.0 => Position::Sub,
903 _ => Position::Baseline,
904 },
905 });
906 }
907 if let Some(transform) = p.attr(&Ns::Fo, "text-transform") {
908 self.uppercase = Some(transform == "uppercase");
909 }
910 }
911}
912
913impl ParagraphProperties {
914 fn apply(&mut self, p: &Element) {
915 if let Some(align) = p.attr(&Ns::Fo, "text-align") {
916 self.align = match align {
917 "start" | "left" => Some(TextAlign::Start),
923 "end" | "right" => Some(TextAlign::End),
924 "center" => Some(TextAlign::Center),
925 "justify" => Some(TextAlign::Justify),
926 _ => self.align,
927 };
928 }
929 read_edges(p, "margin", &mut self.margin, Length::parse);
930 read_edges(p, "padding", &mut self.padding, Length::parse);
931 read_edges(p, "border", &mut self.border, Border::parse);
932 if let Some(indent) = p.attr(&Ns::Fo, "text-indent").and_then(Length::parse) {
933 self.text_indent = Some(indent);
934 }
935 if let Some(height) = p.attr(&Ns::Fo, "line-height") {
936 self.line_height = Measure::parse(height);
937 }
938 if let Some(color) = p.attr(&Ns::Fo, "background-color") {
939 self.background = Color::parse(color);
940 }
941 if let Some(before) = p.attr(&Ns::Fo, "break-before") {
942 self.break_before = Some(parse_break(before));
943 }
944 if let Some(after) = p.attr(&Ns::Fo, "break-after") {
945 self.break_after = Some(parse_break(after));
946 }
947 }
948}
949
950fn parse_break(text: &str) -> Break {
951 match text {
952 "page" => Break::Page,
953 "column" => Break::Column,
954 _ => Break::Auto,
955 }
956}
957
958impl CellProperties {
959 fn apply(&mut self, p: &Element) {
960 if let Some(color) = p.attr(&Ns::Fo, "background-color") {
961 self.background = Color::parse(color);
962 }
963 if let Some(align) = p.attr(&Ns::Style, "vertical-align") {
964 self.vertical_align = match align {
965 "top" => Some(VerticalAlign::Top),
966 "middle" => Some(VerticalAlign::Middle),
967 _ => Some(VerticalAlign::Bottom),
971 };
972 }
973 read_edges(p, "border", &mut self.border, Border::parse);
974 read_edges(p, "padding", &mut self.padding, Length::parse);
975 if let Some(wrap) = p.attr(&Ns::Fo, "wrap-option") {
976 self.wrap = Some(wrap == "wrap");
977 }
978 }
979}
980
981impl GraphicProperties {
982 fn apply(&mut self, p: &Element) {
983 if let Some(kind) = p.attr(&Ns::Draw, "fill") {
988 self.kind = Some(match kind {
989 "solid" => FillKind::Solid,
990 "gradient" => FillKind::Gradient,
991 "bitmap" => FillKind::Bitmap,
992 "hatch" => FillKind::Hatch,
993 _ => FillKind::None,
994 });
995 }
996 if let Some(color) = p.attr(&Ns::Draw, "fill-color").and_then(Color::parse) {
997 self.color = Some(color);
998 }
999 if let Some(name) = p.attr(&Ns::Draw, "fill-gradient-name") {
1000 self.gradient = Some(name.to_owned());
1001 }
1002 if let Some(name) = p.attr(&Ns::Draw, "fill-image-name") {
1006 self.image = (p.attr(&Ns::Style, "repeat").unwrap_or("stretch") == "stretch")
1007 .then(|| name.to_owned());
1008 }
1009
1010 match p.attr(&Ns::Draw, "stroke") {
1011 Some("none") => self.stroke = None,
1012 _ => {
1013 if let Some(color) = p.attr(&Ns::Svg, "stroke-color").and_then(Color::parse) {
1014 self.stroke = Some(color);
1015 }
1016 }
1017 }
1018 if let Some(width) = p.attr(&Ns::Svg, "stroke-width").and_then(Length::parse) {
1019 self.stroke_width = Some(width);
1020 }
1021 if let Some(opacity) = p.attr(&Ns::Draw, "opacity").and_then(Percent::parse) {
1022 self.opacity = Some(opacity.fraction().clamp(0.0, 1.0));
1023 }
1024 }
1025}