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, Copy, PartialEq, Eq)]
30pub enum ListKind {
31 Bullet,
33 Number,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
43pub enum Family {
44 Paragraph,
46 Text,
48 Section,
50 Table,
52 TableColumn,
54 TableRow,
56 TableCell,
58 Graphic,
60 Presentation,
62 DrawingPage,
64 Chart,
66 Ruby,
68 Other(Box<str>),
71}
72
73impl Family {
74 pub fn parse(text: &str) -> Self {
76 match text {
77 "paragraph" => Self::Paragraph,
78 "text" => Self::Text,
79 "section" => Self::Section,
80 "table" => Self::Table,
81 "table-column" => Self::TableColumn,
82 "table-row" => Self::TableRow,
83 "table-cell" => Self::TableCell,
84 "graphic" => Self::Graphic,
85 "presentation" => Self::Presentation,
86 "drawing-page" => Self::DrawingPage,
87 "chart" => Self::Chart,
88 "ruby" => Self::Ruby,
89 other => Self::Other(other.into()),
90 }
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum TextAlign {
97 Start,
99 End,
101 Center,
103 Justify,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum Position {
110 Baseline,
112 Super,
114 Sub,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum VerticalAlign {
121 Top,
123 Middle,
125 Bottom,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum Break {
132 Auto,
134 Page,
136 Column,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq)]
142pub struct Border {
143 pub width: Length,
145 pub color: Color,
147}
148
149impl Border {
150 fn parse(text: &str) -> Option<Self> {
156 let mut width = None;
157 let mut color = None;
158 for word in text.split_whitespace() {
159 match word {
160 "none" | "hidden" => return None,
161 _ => {
162 if let Some(length) = Length::parse(word) {
163 width = Some(length);
164 } else if let Some(parsed) = Color::parse(word) {
165 color = Some(parsed);
166 }
167 }
168 }
169 }
170 Some(Self {
171 width: width.unwrap_or(Length(0.5)),
174 color: color.unwrap_or(Color { r: 0, g: 0, b: 0 }),
175 })
176 }
177}
178
179#[derive(Debug, Clone, Copy, PartialEq)]
181pub struct Edges<T> {
182 pub left: Option<T>,
184 pub right: Option<T>,
186 pub top: Option<T>,
188 pub bottom: Option<T>,
190}
191
192impl<T> Default for Edges<T> {
197 fn default() -> Self {
198 Self {
199 left: None,
200 right: None,
201 top: None,
202 bottom: None,
203 }
204 }
205}
206
207#[derive(Debug, Clone, Default, PartialEq)]
209pub struct TextProperties {
210 pub font_family: Option<String>,
213 pub size: Option<Measure>,
215 pub bold: Option<bool>,
217 pub italic: Option<bool>,
220 pub underline: Option<bool>,
222 pub strike: Option<bool>,
224 pub color: Option<Color>,
226 pub background: Option<Color>,
228 pub position: Option<Position>,
230 pub uppercase: Option<bool>,
232}
233
234impl TextProperties {
235 #[must_use]
237 pub fn over(&self, parent: &Self) -> Self {
238 Self {
239 font_family: self
240 .font_family
241 .clone()
242 .or_else(|| parent.font_family.clone()),
243 size: self.size.or(parent.size),
244 bold: self.bold.or(parent.bold),
245 italic: self.italic.or(parent.italic),
246 underline: self.underline.or(parent.underline),
247 strike: self.strike.or(parent.strike),
248 color: self.color.or(parent.color),
249 background: self.background.or(parent.background),
250 position: self.position.or(parent.position),
251 uppercase: self.uppercase.or(parent.uppercase),
252 }
253 }
254}
255
256#[derive(Debug, Clone, Default, PartialEq)]
258pub struct ParagraphProperties {
259 pub align: Option<TextAlign>,
261 pub margin: Edges<Length>,
263 pub text_indent: Option<Length>,
265 pub line_height: Option<Measure>,
267 pub background: Option<Color>,
269 pub break_before: Option<Break>,
271 pub break_after: Option<Break>,
273 pub border: Edges<Border>,
275 pub padding: Edges<Length>,
277}
278
279#[derive(Debug, Clone, Default, PartialEq)]
281pub struct CellProperties {
282 pub background: Option<Color>,
284 pub vertical_align: Option<VerticalAlign>,
286 pub border: Edges<Border>,
288 pub padding: Edges<Length>,
290 pub wrap: Option<bool>,
292}
293
294#[derive(Debug, Clone, Default, PartialEq)]
301pub enum Fill {
302 #[default]
304 None,
305 Solid(Color),
307 Gradient(String),
309 Image(String),
316}
317
318#[derive(Debug, Clone, Copy, PartialEq, Eq)]
320pub enum GradientStyle {
321 Linear,
323 Axial,
325 Radial,
327 Ellipsoidal,
329 Square,
331 Rectangular,
333}
334
335#[derive(Debug, Clone, PartialEq)]
344pub struct Gradient {
345 pub style: GradientStyle,
347 pub start: Color,
349 pub end: Color,
351 pub angle: f32,
354 pub border: f32,
357 pub center: (f32, f32),
360}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq)]
364enum FillKind {
365 None,
366 Solid,
367 Gradient,
368 Bitmap,
370 Hatch,
372}
373
374#[derive(Debug, Clone, Default, PartialEq)]
384pub struct GraphicProperties {
385 kind: Option<FillKind>,
386 color: Option<Color>,
387 gradient: Option<String>,
388 image: Option<String>,
389 pub stroke: Option<Color>,
391 pub stroke_width: Option<Length>,
393 pub opacity: Option<f32>,
395 pub text_anchor: Option<Anchor>,
397 pub decorative: Option<bool>,
401}
402
403#[derive(Debug, Clone, Copy, PartialEq, Eq)]
410pub enum Anchor {
411 Top,
413 Middle,
415 Bottom,
417}
418
419impl Anchor {
420 #[must_use]
422 pub fn share(self) -> f32 {
423 match self {
424 Self::Top => 0.0,
425 Self::Middle => 0.5,
426 Self::Bottom => 1.0,
427 }
428 }
429}
430
431impl GraphicProperties {
432 pub fn fill(&self) -> Fill {
439 match self.kind {
440 None | Some(FillKind::None | FillKind::Hatch) => Fill::None,
441 Some(FillKind::Solid) => self.color.map_or(Fill::None, Fill::Solid),
442 Some(FillKind::Gradient) => self.gradient.clone().map_or(Fill::None, Fill::Gradient),
443 Some(FillKind::Bitmap) => self.image.clone().map_or(Fill::None, Fill::Image),
444 }
445 }
446}
447
448#[derive(Debug, Clone, Default, PartialEq)]
455pub struct Properties {
456 pub text: TextProperties,
458 pub paragraph: ParagraphProperties,
460 pub cell: CellProperties,
462 pub graphic: GraphicProperties,
464 pub background_visible: Option<bool>,
467 pub background_objects_visible: Option<bool>,
469 pub column_width: Option<Length>,
471 pub row_height: Option<Length>,
473}
474
475#[derive(Debug, Clone, PartialEq)]
477pub struct PageLayout {
478 pub width: Length,
480 pub height: Length,
482 pub margin: Edges<Length>,
484}
485
486impl Default for PageLayout {
487 fn default() -> Self {
496 Self {
497 width: Length(612.0),
498 height: Length(792.0),
499 margin: Edges {
500 left: Some(Length(72.0)),
501 right: Some(Length(72.0)),
502 top: Some(Length(72.0)),
503 bottom: Some(Length(72.0)),
504 },
505 }
506 }
507}
508
509pub struct Style {
511 pub name: String,
513 pub display_name: Option<String>,
517 pub family: Family,
519 pub parent: Option<String>,
521 pub element: Element,
524}
525
526pub struct Styles {
528 by_name: HashMap<(Family, String), Style>,
529 defaults: HashMap<Family, Element>,
530 page_layouts: HashMap<String, PageLayout>,
531 master_pages: HashMap<String, Element>,
532 lists: HashMap<String, Element>,
533 gradients: HashMap<String, Gradient>,
534 fill_images: HashMap<String, String>,
535 font_faces: HashMap<String, String>,
536 cache: RefCell<HashMap<(Family, String), Rc<Properties>>>,
537}
538
539impl Styles {
540 pub fn collect(content: Option<&Element>, styles: Option<&Element>) -> Self {
546 let mut this = Self {
547 by_name: HashMap::new(),
548 defaults: HashMap::new(),
549 page_layouts: HashMap::new(),
550 master_pages: HashMap::new(),
551 lists: HashMap::new(),
552 gradients: HashMap::new(),
553 fill_images: HashMap::new(),
554 font_faces: HashMap::new(),
555 cache: RefCell::new(HashMap::new()),
556 };
557 for root in [styles, content].into_iter().flatten() {
561 this.collect_from(root);
562 }
563 this
564 }
565
566 fn collect_from(&mut self, root: &Element) {
567 for container in root.elements() {
568 match () {
569 () if container.is(&Ns::Office, "font-face-decls") => {
570 for face in container.elements() {
571 let Some(name) = face.attr(&Ns::Style, "name") else {
572 continue;
573 };
574 let family = face
579 .attr(&Ns::Svg, "font-family")
580 .unwrap_or(name)
581 .trim_matches('\'')
582 .to_owned();
583 self.font_faces.insert(name.to_owned(), family);
584 }
585 }
586 () if container.is(&Ns::Office, "styles")
587 || container.is(&Ns::Office, "automatic-styles") =>
588 {
589 for element in container.elements() {
590 self.collect_style(element);
591 }
592 }
593 () if container.is(&Ns::Office, "master-styles") => {
594 for master in container.elements() {
595 if let Some(name) = master.attr(&Ns::Style, "name") {
596 self.master_pages.insert(name.to_owned(), master.clone());
597 }
598 }
599 }
600 () => {}
601 }
602 }
603 }
604
605 fn collect_style(&mut self, element: &Element) {
606 if element.is(&Ns::Style, "style") {
607 let Some(name) = element.attr(&Ns::Style, "name") else {
608 return;
609 };
610 let family = Family::parse(element.attr(&Ns::Style, "family").unwrap_or_default());
611 let style = Style {
612 name: name.to_owned(),
613 display_name: element
614 .attr(&Ns::Style, "display-name")
615 .map(ToOwned::to_owned),
616 family: family.clone(),
617 parent: element
618 .attr(&Ns::Style, "parent-style-name")
619 .map(ToOwned::to_owned),
620 element: element.clone(),
621 };
622 self.by_name.insert((family, name.to_owned()), style);
623 } else if element.is(&Ns::Style, "default-style") {
624 let family = Family::parse(element.attr(&Ns::Style, "family").unwrap_or_default());
625 self.defaults.insert(family, element.clone());
626 } else if element.is(&Ns::Style, "page-layout") {
627 if let Some(name) = element.attr(&Ns::Style, "name") {
628 self.page_layouts
629 .insert(name.to_owned(), page_layout(element));
630 }
631 } else if element.is(&Ns::Draw, "fill-image") {
632 if let (Some(name), Some(href)) = (
633 element.attr(&Ns::Draw, "name"),
634 element.attr(&Ns::Xlink, "href"),
635 ) {
636 self.fill_images.insert(name.to_owned(), href.to_owned());
637 }
638 } else if element.is(&Ns::Draw, "gradient") {
639 if let Some(name) = element.attr(&Ns::Draw, "name") {
640 self.gradients.insert(name.to_owned(), gradient(element));
641 }
642 } else if element.is(&Ns::Text, "list-style")
643 && let Some(name) = element.attr(&Ns::Style, "name")
644 {
645 self.lists.insert(name.to_owned(), element.clone());
646 }
647 }
648
649 pub fn add(&mut self, element: &Element) {
656 if let (Some(name), Some(family)) = (
657 element.attr(&Ns::Style, "name"),
658 element.attr(&Ns::Style, "family"),
659 ) {
660 let key = (Family::parse(family), name.to_owned());
661 self.cache.borrow_mut().remove(&key);
662 }
663 self.collect_style(element);
664 }
665
666 pub fn style(&self, family: &Family, name: &str) -> Option<&Style> {
668 self.by_name.get(&(family.clone(), name.to_owned()))
669 }
670
671 pub fn gradient(&self, name: &str) -> Option<&Gradient> {
673 self.gradients.get(name)
674 }
675
676 pub fn fill_image(&self, name: &str) -> Option<&str> {
678 self.fill_images.get(name).map(String::as_str)
679 }
680
681 pub fn heading_style(&self, level: u8) -> Option<&str> {
685 let wanted = level.to_string();
686 let declared = self
687 .by_name
688 .iter()
689 .filter(|((family, _), style)| {
690 *family == Family::Paragraph
691 && style.element.attr(&Ns::Style, "default-outline-level")
692 == Some(wanted.as_str())
693 })
694 .map(|((_, name), _)| name.as_str())
695 .min();
696 declared.or_else(|| {
697 let conventional = format!("Heading_20_{level}");
698 self.by_name
699 .get_key_value(&(Family::Paragraph, conventional))
700 .map(|((_, name), _)| name.as_str())
701 })
702 }
703
704 pub fn has_style(&self, family: &Family, name: &str) -> bool {
706 self.by_name
707 .contains_key(&(family.clone(), name.to_owned()))
708 }
709
710 pub fn list_kind(&self, name: &str) -> Option<ListKind> {
713 self.lists.get(name)?.elements().find_map(|level| {
714 if level.name.ns != Ns::Text {
715 return None;
716 }
717 match &*level.name.local {
718 "list-level-style-number" => Some(ListKind::Number),
719 "list-level-style-bullet" | "list-level-style-image" => Some(ListKind::Bullet),
720 _ => None,
721 }
722 })
723 }
724
725 pub fn list_style_for(&self, kind: ListKind) -> Option<&str> {
727 self.lists
728 .keys()
729 .filter(|name| self.list_kind(name) == Some(kind))
730 .map(String::as_str)
731 .min()
732 }
733
734 pub fn list_style(&self, name: &str) -> Option<&Element> {
737 self.lists.get(name)
738 }
739
740 pub fn master_page(&self, name: &str) -> Option<&Element> {
742 self.master_pages.get(name)
743 }
744
745 pub fn page_layout_of(&self, master_page: &str) -> Option<&PageLayout> {
747 let master = self.master_pages.get(master_page)?;
748 let layout = master.attr(&Ns::Style, "page-layout-name")?;
749 self.page_layouts.get(layout)
750 }
751
752 pub fn resolve(&self, family: &Family, name: &str) -> Rc<Properties> {
759 let key = (family.clone(), name.to_owned());
760 if let Some(cached) = self.cache.borrow().get(&key) {
761 return Rc::clone(cached);
762 }
763
764 let mut properties = Properties::default();
765 if let Some(default) = self.defaults.get(family) {
766 properties.apply(default, &self.font_faces);
767 }
768 for style in self.chain(family, name).into_iter().rev() {
770 properties.apply(&style.element, &self.font_faces);
771 }
772
773 let properties = Rc::new(properties);
774 self.cache.borrow_mut().insert(key, Rc::clone(&properties));
775 properties
776 }
777
778 fn chain(&self, family: &Family, name: &str) -> Vec<&Style> {
783 let mut chain: Vec<&Style> = Vec::new();
784 let mut next: Option<&str> = Some(name);
785 while let Some(current) = next {
786 if chain.iter().any(|s| s.name == current) {
787 break;
788 }
789 let Some(style) = self.style(family, current) else {
790 break;
791 };
792 next = style.parent.as_deref();
793 chain.push(style);
794 }
795 chain
796 }
797
798 pub fn font_family(&self, declared: &str) -> String {
802 self.font_faces
803 .get(declared)
804 .cloned()
805 .unwrap_or_else(|| declared.to_owned())
806 }
807}
808
809fn gradient(element: &Element) -> Gradient {
810 let color = |local: &str, fallback: Color| {
811 element
812 .attr(&Ns::Draw, local)
813 .and_then(Color::parse)
814 .unwrap_or(fallback)
815 };
816 let proportion = |local: &str| {
817 element
818 .attr(&Ns::Draw, local)
819 .and_then(Percent::parse)
820 .map_or(0.0, Percent::fraction)
821 };
822 Gradient {
823 style: match element.attr(&Ns::Draw, "style") {
824 Some("axial") => GradientStyle::Axial,
825 Some("radial") => GradientStyle::Radial,
826 Some("ellipsoid") => GradientStyle::Ellipsoidal,
827 Some("square") => GradientStyle::Square,
828 Some("rectangular") => GradientStyle::Rectangular,
829 _ => GradientStyle::Linear,
830 },
831 start: color("start-color", Color { r: 0, g: 0, b: 0 }),
832 end: color(
833 "end-color",
834 Color {
835 r: 0xff,
836 g: 0xff,
837 b: 0xff,
838 },
839 ),
840 angle: element.attr(&Ns::Draw, "angle").map_or(0.0, parse_angle),
843 border: proportion("border"),
844 center: (proportion("cx"), proportion("cy")),
845 }
846}
847
848fn parse_angle(text: &str) -> f32 {
854 let text = text.trim();
855 match text.strip_suffix("deg") {
856 Some(degrees) => degrees.trim().parse().unwrap_or(0.0),
857 None => text.parse::<f32>().unwrap_or(0.0) / 10.0,
858 }
859}
860
861fn page_layout(element: &Element) -> PageLayout {
862 let mut layout = PageLayout::default();
863 if let Some(properties) = element.child(&Ns::Style, "page-layout-properties") {
864 if let Some(width) = properties
865 .attr(&Ns::Fo, "page-width")
866 .and_then(Length::parse)
867 {
868 layout.width = width;
869 }
870 if let Some(height) = properties
871 .attr(&Ns::Fo, "page-height")
872 .and_then(Length::parse)
873 {
874 layout.height = height;
875 }
876 let mut margin = Edges::default();
877 read_edges(properties, "margin", &mut margin, Length::parse);
878 layout.margin.left = margin.left.or(layout.margin.left);
882 layout.margin.right = margin.right.or(layout.margin.right);
883 layout.margin.top = margin.top.or(layout.margin.top);
884 layout.margin.bottom = margin.bottom.or(layout.margin.bottom);
885 }
886 layout
887}
888
889fn read_edges<T: Copy>(
892 properties: &Element,
893 base: &str,
894 into: &mut Edges<T>,
895 parse: impl Fn(&str) -> Option<T>,
896) {
897 if let Some(all) = properties.attr(&Ns::Fo, base).and_then(&parse) {
898 *into = Edges {
899 left: Some(all),
900 right: Some(all),
901 top: Some(all),
902 bottom: Some(all),
903 };
904 }
905 if let Some(v) = properties
906 .attr(&Ns::Fo, &format!("{base}-left"))
907 .and_then(&parse)
908 {
909 into.left = Some(v);
910 }
911 if let Some(v) = properties
912 .attr(&Ns::Fo, &format!("{base}-right"))
913 .and_then(&parse)
914 {
915 into.right = Some(v);
916 }
917 if let Some(v) = properties
918 .attr(&Ns::Fo, &format!("{base}-top"))
919 .and_then(&parse)
920 {
921 into.top = Some(v);
922 }
923 if let Some(v) = properties
924 .attr(&Ns::Fo, &format!("{base}-bottom"))
925 .and_then(&parse)
926 {
927 into.bottom = Some(v);
928 }
929}
930
931impl Properties {
932 fn apply(&mut self, style: &Element, font_faces: &HashMap<String, String>) {
938 for properties in style.elements() {
939 if properties.is(&Ns::Style, "text-properties") {
940 self.text.apply(properties, font_faces);
941 } else if properties.is(&Ns::Style, "paragraph-properties") {
942 self.paragraph.apply(properties);
943 } else if properties.is(&Ns::Style, "table-cell-properties") {
944 self.cell.apply(properties);
945 } else if properties.is(&Ns::Style, "graphic-properties")
946 || properties.is(&Ns::Style, "drawing-page-properties")
947 {
948 self.graphic.apply(properties);
949 if let Some(visible) = properties
953 .attr(&Ns::Presentation, "background-visible")
954 .and_then(crate::value::boolean)
955 {
956 self.background_visible = Some(visible);
957 }
958 if let Some(visible) = properties
959 .attr(&Ns::Presentation, "background-objects-visible")
960 .and_then(crate::value::boolean)
961 {
962 self.background_objects_visible = Some(visible);
963 }
964 } else if properties.is(&Ns::Style, "table-column-properties") {
965 if let Some(width) = properties
966 .attr(&Ns::Style, "column-width")
967 .and_then(Length::parse)
968 {
969 self.column_width = Some(width);
970 }
971 } else if properties.is(&Ns::Style, "table-row-properties")
972 && let Some(height) = properties
973 .attr(&Ns::Style, "row-height")
974 .and_then(Length::parse)
975 {
976 self.row_height = Some(height);
977 }
978 }
979 }
980}
981
982impl TextProperties {
983 fn apply(&mut self, p: &Element, font_faces: &HashMap<String, String>) {
984 if let Some(name) = p.attr(&Ns::Fo, "font-family") {
988 self.font_family = Some(name.trim_matches('\'').to_owned());
989 }
990 if let Some(name) = p.attr(&Ns::Style, "font-name") {
991 self.font_family = Some(
992 font_faces
993 .get(name)
994 .cloned()
995 .unwrap_or_else(|| name.to_owned()),
996 );
997 }
998 if let Some(size) = p.attr(&Ns::Fo, "font-size").and_then(Measure::parse) {
999 self.size = Some(size);
1000 }
1001 if let Some(weight) = p.attr(&Ns::Fo, "font-weight") {
1002 self.bold = Some(match weight {
1005 "normal" => false,
1006 "bold" => true,
1007 other => other.parse::<u32>().is_ok_and(|n| n >= 600),
1008 });
1009 }
1010 if let Some(style) = p.attr(&Ns::Fo, "font-style") {
1011 self.italic = Some(style != "normal");
1012 }
1013 if let Some(line) = p.attr(&Ns::Style, "text-underline-style") {
1014 self.underline = Some(line != "none");
1015 }
1016 if let Some(line) = p.attr(&Ns::Style, "text-line-through-style") {
1017 self.strike = Some(line != "none");
1018 }
1019 if let Some(color) = p.attr(&Ns::Fo, "color") {
1020 self.color = Color::parse(color);
1021 }
1022 if let Some(color) = p.attr(&Ns::Fo, "background-color") {
1023 self.background = Color::parse(color);
1024 }
1025 if let Some(position) = p.attr(&Ns::Style, "text-position") {
1026 let first = position.split_whitespace().next().unwrap_or_default();
1032 self.position = Some(match first {
1033 "super" => Position::Super,
1034 "sub" => Position::Sub,
1035 _ => match Percent::parse(first) {
1036 Some(percent) if percent.0 > 0.0 => Position::Super,
1037 Some(percent) if percent.0 < 0.0 => Position::Sub,
1038 _ => Position::Baseline,
1039 },
1040 });
1041 }
1042 if let Some(transform) = p.attr(&Ns::Fo, "text-transform") {
1043 self.uppercase = Some(transform == "uppercase");
1044 }
1045 }
1046}
1047
1048impl ParagraphProperties {
1049 fn apply(&mut self, p: &Element) {
1050 if let Some(align) = p.attr(&Ns::Fo, "text-align") {
1051 self.align = match align {
1052 "start" | "left" => Some(TextAlign::Start),
1058 "end" | "right" => Some(TextAlign::End),
1059 "center" => Some(TextAlign::Center),
1060 "justify" => Some(TextAlign::Justify),
1061 _ => self.align,
1062 };
1063 }
1064 read_edges(p, "margin", &mut self.margin, Length::parse);
1065 read_edges(p, "padding", &mut self.padding, Length::parse);
1066 read_edges(p, "border", &mut self.border, Border::parse);
1067 if let Some(indent) = p.attr(&Ns::Fo, "text-indent").and_then(Length::parse) {
1068 self.text_indent = Some(indent);
1069 }
1070 if let Some(height) = p.attr(&Ns::Fo, "line-height") {
1071 self.line_height = Measure::parse(height);
1072 }
1073 if let Some(color) = p.attr(&Ns::Fo, "background-color") {
1074 self.background = Color::parse(color);
1075 }
1076 if let Some(before) = p.attr(&Ns::Fo, "break-before") {
1077 self.break_before = Some(parse_break(before));
1078 }
1079 if let Some(after) = p.attr(&Ns::Fo, "break-after") {
1080 self.break_after = Some(parse_break(after));
1081 }
1082 }
1083}
1084
1085fn parse_break(text: &str) -> Break {
1086 match text {
1087 "page" => Break::Page,
1088 "column" => Break::Column,
1089 _ => Break::Auto,
1090 }
1091}
1092
1093impl CellProperties {
1094 fn apply(&mut self, p: &Element) {
1095 if let Some(color) = p.attr(&Ns::Fo, "background-color") {
1096 self.background = Color::parse(color);
1097 }
1098 if let Some(align) = p.attr(&Ns::Style, "vertical-align") {
1099 self.vertical_align = match align {
1100 "top" => Some(VerticalAlign::Top),
1101 "middle" => Some(VerticalAlign::Middle),
1102 _ => Some(VerticalAlign::Bottom),
1106 };
1107 }
1108 read_edges(p, "border", &mut self.border, Border::parse);
1109 read_edges(p, "padding", &mut self.padding, Length::parse);
1110 if let Some(wrap) = p.attr(&Ns::Fo, "wrap-option") {
1111 self.wrap = Some(wrap == "wrap");
1112 }
1113 }
1114}
1115
1116impl GraphicProperties {
1117 fn apply(&mut self, p: &Element) {
1118 if let Some(kind) = p.attr(&Ns::Draw, "fill") {
1123 self.kind = Some(match kind {
1124 "solid" => FillKind::Solid,
1125 "gradient" => FillKind::Gradient,
1126 "bitmap" => FillKind::Bitmap,
1127 "hatch" => FillKind::Hatch,
1128 _ => FillKind::None,
1129 });
1130 }
1131 if let Some(color) = p.attr(&Ns::Draw, "fill-color").and_then(Color::parse) {
1132 self.color = Some(color);
1133 }
1134 if let Some(name) = p.attr(&Ns::Draw, "fill-gradient-name") {
1135 self.gradient = Some(name.to_owned());
1136 }
1137 if let Some(name) = p.attr(&Ns::Draw, "fill-image-name") {
1141 self.image = (p.attr(&Ns::Style, "repeat").unwrap_or("stretch") == "stretch")
1142 .then(|| name.to_owned());
1143 }
1144
1145 match p.attr(&Ns::Draw, "stroke") {
1146 Some("none") => self.stroke = None,
1147 _ => {
1148 if let Some(color) = p.attr(&Ns::Svg, "stroke-color").and_then(Color::parse) {
1149 self.stroke = Some(color);
1150 }
1151 }
1152 }
1153 if let Some(width) = p.attr(&Ns::Svg, "stroke-width").and_then(Length::parse) {
1154 self.stroke_width = Some(width);
1155 }
1156 if let Some(opacity) = p.attr(&Ns::Draw, "opacity").and_then(Percent::parse) {
1157 self.opacity = Some(opacity.fraction().clamp(0.0, 1.0));
1158 }
1159 if let Some(anchor) = p.attr(&Ns::Draw, "textarea-vertical-align") {
1163 self.text_anchor = Some(match anchor {
1164 "middle" => Anchor::Middle,
1165 "bottom" => Anchor::Bottom,
1166 _ => Anchor::Top,
1167 });
1168 }
1169 if let Some(decorative) = p
1170 .attr(&Ns::Loext, "decorative")
1171 .and_then(crate::value::boolean)
1172 {
1173 self.decorative = Some(decorative);
1174 }
1175 }
1176}