impl From<TableProperties> for Look {
fn from(table_properties: TableProperties) -> Self {
Self {
- name: table_properties.name,
- hide_empty: table_properties.general_properties.hide_empty_rows,
- row_label_position: table_properties.general_properties.row_label_position,
- heading_widths: enum_map! {
- HeadingRegion::Columns => table_properties.general_properties.minimum_column_width..=table_properties.general_properties.maximum_column_width,
- HeadingRegion::Rows => table_properties.general_properties.minimum_row_width..=table_properties.general_properties.maximum_row_width,
- }.map(|_k, r|(*r.start()).try_into().unwrap_or_default()..=(*r.end()).try_into().unwrap_or_default()),
- footnote_marker_type: table_properties.footnote_properties.marker_type,
- footnote_marker_position: table_properties.footnote_properties.marker_position,
- areas: enum_map! {
- Area::Title => table_properties.cell_format_properties.title.style.as_area_style(RowParity::Even),
- Area::Caption => table_properties.cell_format_properties.caption.style.as_area_style(RowParity::Even),
- Area::Footer => table_properties.cell_format_properties.footnotes.style.as_area_style(RowParity::Even),
- Area::Corner => table_properties.cell_format_properties.corner_labels.style.as_area_style(RowParity::Even),
- Area::Labels(Axis2::X) => table_properties.cell_format_properties.column_labels.style.as_area_style(RowParity::Even),
- Area::Labels(Axis2::Y) => table_properties.cell_format_properties.row_labels.style.as_area_style(RowParity::Even),
- Area::Data(row) => table_properties.cell_format_properties.data.style.as_area_style(row),
- Area::Layers => table_properties.cell_format_properties.layers.style.as_area_style(RowParity::Even),
- },
+ name: table_properties.name,
+ hide_empty: table_properties.general_properties.hide_empty_rows,
+ row_label_position: table_properties.general_properties.row_label_position,
+ heading_widths: enum_map! {
+ HeadingRegion::Columns => table_properties.general_properties.minimum_column_width..=table_properties.general_properties.maximum_column_width,
+ HeadingRegion::Rows => table_properties.general_properties.minimum_row_width..=table_properties.general_properties.maximum_row_width,
+ }.map(|_k, r|(*r.start()).try_into().unwrap_or_default()..=(*r.end()).try_into().unwrap_or_default()),
+ footnote_marker_type: table_properties.footnote_properties.marker_type,
+ footnote_marker_position: table_properties.footnote_properties.marker_position,
+ areas: EnumMap::from_fn(|area| {
+ table_properties.cell_format_properties.get_style(area).as_area_style(area)
+ }),
borders: table_properties.border_properties.decode(),
- print_all_layers: table_properties.printing_properties.print_all_layers,
- paginate_layers: table_properties
- .printing_properties
- .print_each_layer_on_separate_page,
- shrink_to_fit: enum_map! {
- Axis2::X => table_properties.printing_properties.rescale_wide_table_to_fit_page,
- Axis2::Y => table_properties.printing_properties.rescale_long_table_to_fit_page,
- },
+ print_all_layers: table_properties.printing_properties.print_all_layers,
+ paginate_layers: table_properties
+ .printing_properties
+ .print_each_layer_on_separate_page,
+ shrink_to_fit: enum_map! {
+ Axis2::X => table_properties.printing_properties.rescale_wide_table_to_fit_page,
+ Axis2::Y => table_properties.printing_properties.rescale_long_table_to_fit_page,
+ },
show_continuations: [ table_properties
- .printing_properties
- .continuation_text_at_top,
- table_properties
- .printing_properties
- .continuation_text_at_bottom],
- continuation: {
- let text = table_properties.printing_properties.continuation_text;
- if text.is_empty() {
- None
- } else {
- Some(text)
- }
- },
- n_orphan_lines: table_properties
- .printing_properties
- .window_orphan_lines
- .try_into()
- .unwrap_or_default(),
- }
+ .printing_properties
+ .continuation_text_at_top,
+ table_properties
+ .printing_properties
+ .continuation_text_at_bottom],
+ continuation: {
+ let text = table_properties.printing_properties.continuation_text;
+ if text.is_empty() {
+ None
+ } else {
+ Some(text)
+ }
+ },
+ n_orphan_lines: table_properties
+ .printing_properties
+ .window_orphan_lines
+ .try_into()
+ .unwrap_or_default(),
+ }
}
}
fn default() -> Self {
Self {
hide_empty_rows: true,
- maximum_column_width: 120,
- minimum_column_width: 36,
- maximum_row_width: 72,
+ maximum_column_width: 72,
+ minimum_column_width: 50,
+ maximum_row_width: 120,
minimum_row_width: 36,
row_label_position: LabelPosition::Corner,
}
title: CellStyleHolder,
}
+impl CellFormatProperties {
+ fn get_style(&self, area: Area) -> &CellStyle {
+ match area {
+ Area::Title => &self.title.style,
+ Area::Caption => &self.caption.style,
+ Area::Footer => &self.footnotes.style,
+ Area::Corner => &self.corner_labels.style,
+ Area::Labels(Axis2::X) => &self.column_labels.style,
+ Area::Labels(Axis2::Y) => &self.row_labels.style,
+ Area::Data(_) => &self.data.style,
+ Area::Layers => &self.layers.style,
+ }
+ }
+}
+
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CellStyleHolder {
#[serde(rename = "@color2")]
color2: Option<Color>,
#[serde(rename = "@font-family")]
- font_family: String,
+ font_family: Option<String>,
#[serde(rename = "@font-size")]
- font_size: Length,
+ font_size: Option<Length>,
#[serde(rename = "@font-style")]
- font_style: FontStyle,
+ font_style: Option<FontStyle>,
#[serde(rename = "@font-weight")]
- font_weight: FontWeight,
+ font_weight: Option<FontWeight>,
#[serde(rename = "@font-underline")]
- font_underline: FontUnderline,
+ font_underline: Option<FontUnderline>,
#[serde(rename = "@labelLocationVertical")]
- label_location_vertical: LabelLocationVertical,
+ label_location_vertical: Option<LabelLocationVertical>,
#[serde(rename = "@margin-bottom")]
- margin_bottom: Length,
+ margin_bottom: Option<Length>,
#[serde(rename = "@margin-left")]
- margin_left: Length,
+ margin_left: Option<Length>,
#[serde(rename = "@margin-right")]
- margin_right: Length,
+ margin_right: Option<Length>,
#[serde(rename = "@margin-top")]
- margin_top: Length,
- #[serde(rename = "@textAlignment", default)]
- text_alignment: TextAlignment,
+ margin_top: Option<Length>,
+ #[serde(rename = "@textAlignment")]
+ text_alignment: Option<TextAlignment>,
#[serde(rename = "@decimal-offset")]
- decimal_offset: Length,
+ decimal_offset: Option<Length>,
}
impl CellStyle {
- fn as_area_style(&self, data_row: RowParity) -> AreaStyle {
+ fn default_area_style(area: Area) -> AreaStyle {
+ use HorzAlign::*;
+ use VertAlign::*;
+ const BLACKISH: Color = Color::new(0x01, 0x02, 0x05);
+ const WHITE: Color = Color::WHITE;
+ const DARK_BLUE: Color = Color::new(0x26, 0x4a, 0x60);
+ const LIGHT_GRAY: Color = Color::new(0xe0, 0xe0, 0xe0);
+ const VERY_LIGHT_GRAY: Color = Color::new(0xf9, 0xf9, 0xfb);
+ let (horz_align, vert_align, hmargins, vmargins, fg, bg, size) = match area {
+ Area::Title => (Some(Center), Middle, [6, 8], [1, 6], BLACKISH, WHITE, 11),
+ Area::Caption => (Some(Left), Top, [6, 8], [1, 1], BLACKISH, WHITE, 9),
+ Area::Footer => (Some(Left), Top, [18, 18], [2, 3], BLACKISH, WHITE, 9),
+ Area::Corner => (Some(Left), Bottom, [6, 8], [3, 1], DARK_BLUE, WHITE, 9),
+ Area::Labels(Axis2::X) => (Some(Center), Bottom, [6, 8], [2, 2], DARK_BLUE, WHITE, 9),
+ Area::Labels(Axis2::Y) => (Some(Left), Top, [6, 8], [3, 2], DARK_BLUE, LIGHT_GRAY, 9),
+ Area::Data(_) => (None, Top, [6, 8], [3, 2], DARK_BLUE, VERY_LIGHT_GRAY, 9),
+ Area::Layers => (Some(Left), Bottom, [6, 8], [1, 3], BLACKISH, WHITE, 9),
+ };
+ let bold = area == Area::Title;
AreaStyle {
cell_style: look::CellStyle {
- horz_align: match self.text_alignment {
- TextAlignment::Left => Some(HorzAlign::Left),
- TextAlignment::Right => Some(HorzAlign::Right),
- TextAlignment::Center => Some(HorzAlign::Center),
- TextAlignment::Decimal => Some(HorzAlign::Decimal {
- offset: self.decimal_offset.as_px_f64(),
- }),
- TextAlignment::Mixed => None,
- },
- vert_align: match self.label_location_vertical {
- LabelLocationVertical::Positive => VertAlign::Top,
- LabelLocationVertical::Negative => VertAlign::Bottom,
- LabelLocationVertical::Center => VertAlign::Middle,
- },
+ horz_align,
+ vert_align,
margins: enum_map! {
- Axis2::X => [self.margin_left.as_px_i32(), self.margin_right.as_px_i32()],
- Axis2::Y => [self.margin_top.as_px_i32(), self.margin_bottom.as_px_i32()],
+ Axis2::X => hmargins,
+ Axis2::Y => vmargins,
},
},
font_style: look::FontStyle {
- bold: self.font_weight == FontWeight::Bold,
- italic: self.font_style == FontStyle::Italic,
- underline: self.font_underline == FontUnderline::Underline,
- font: self.font_family.clone(),
- fg: match data_row {
- RowParity::Even => self.color.unwrap_or(Color::BLACK),
- RowParity::Odd => self.alternating_text_color.unwrap_or(Color::BLACK),
- },
- bg: match data_row {
- RowParity::Even => self.color2.unwrap_or(Color::WHITE),
- RowParity::Odd => self.alternating_color.unwrap_or(Color::WHITE),
- },
- size: self.font_size.as_pt_i32(),
+ bold,
+ italic: false,
+ underline: false,
+ font: String::from("SansSerif"),
+ fg,
+ bg,
+ size,
},
}
}
+
+ fn as_area_style(&self, area: Area) -> AreaStyle {
+ let mut style = Self::default_area_style(area);
+
+ if let Some(text_alignment) = self.text_alignment {
+ style.cell_style.horz_align = match text_alignment {
+ TextAlignment::Left => Some(HorzAlign::Left),
+ TextAlignment::Right => Some(HorzAlign::Right),
+ TextAlignment::Center => Some(HorzAlign::Center),
+ TextAlignment::Decimal => Some(HorzAlign::Decimal {
+ offset: self.decimal_offset.map_or(0.0, |offset| offset.as_px_f64()),
+ }),
+ TextAlignment::Mixed => None,
+ };
+ }
+
+ if let Some(label_location_vertical) = self.label_location_vertical {
+ style.cell_style.vert_align = match label_location_vertical {
+ LabelLocationVertical::Positive => VertAlign::Top,
+ LabelLocationVertical::Negative => VertAlign::Bottom,
+ LabelLocationVertical::Center => VertAlign::Middle,
+ };
+ }
+
+ if let Some(margin_left) = self.margin_left {
+ style.cell_style.margins[Axis2::X][0] = margin_left.as_px_i32();
+ }
+ if let Some(margin_right) = self.margin_right {
+ style.cell_style.margins[Axis2::X][1] = margin_right.as_px_i32();
+ }
+ if let Some(margin_top) = self.margin_top {
+ style.cell_style.margins[Axis2::Y][0] = margin_top.as_px_i32();
+ }
+ if let Some(margin_bottom) = self.margin_bottom {
+ style.cell_style.margins[Axis2::Y][1] = margin_bottom.as_px_i32();
+ }
+
+ if let Some(font_weight) = self.font_weight {
+ style.font_style.bold = font_weight == FontWeight::Bold;
+ }
+ if let Some(font_style) = self.font_style {
+ style.font_style.italic = font_style == FontStyle::Italic;
+ }
+ if let Some(font_underline) = self.font_underline {
+ style.font_style.underline = font_underline == FontUnderline::Underline;
+ }
+ if let Some(font_family) = &self.font_family {
+ style.font_style.font = font_family.clone();
+ }
+
+ let (fg, bg) = if area.row_parity() == Some(RowParity::Odd)
+ && (self.alternating_text_color.is_some() || self.alternating_color.is_some())
+ {
+ (self.alternating_text_color, self.alternating_color)
+ } else {
+ (self.color, self.color2)
+ };
+ if fg.is_some() || bg.is_some() {
+ style.font_style.fg = fg.unwrap_or(Color::BLACK);
+ style.font_style.bg = bg.unwrap_or(Color::WHITE);
+ };
+ if let Some(font_size) = self.font_size {
+ style.font_style.size = font_size.as_pt_i32();
+ }
+ style
+ }
}
-#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
enum FontStyle {
#[default]
Italic,
}
-#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
enum FontWeight {
#[default]
Bold,
}
-#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
enum FontUnderline {
#[default]
Underline,
}
-#[derive(Clone, Debug, Default, Deserialize)]
+#[derive(Copy, Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
enum TextAlignment {
Left,
Mixed,
}
-#[derive(Clone, Debug, Default, Deserialize)]
+#[derive(Copy, Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
enum LabelLocationVertical {
/// Top.
fn decode(&self) -> EnumMap<Border, look::BorderStyle> {
EnumMap::from_fn(|border: Border| {
- let mut base = border.default_border_style();
+ let mut base = Self::default_border_style(border);
if let Some(style) = self.get_style(border) {
if let Some(stroke) = style.stroke {
base.stroke = stroke;
base
})
}
+
+ /// Returns the default border style for `border` when reading an XML
+ /// [Look]. (This is different from the system default.)
+ pub fn default_border_style(border: Border) -> look::BorderStyle {
+ const VERY_DARK_BLUE: Color = Color::new(0x15, 0x29, 0x35);
+ const LIGHT_GRAY: Color = Color::new(0xe0, 0xe0, 0xe0);
+ const GRAY: Color = Color::new(0xae, 0xae, 0xae);
+
+ match border {
+ Border::InnerFrame(_) | Border::OuterFrame(_) | Border::Title => {
+ look::BorderStyle::none().with_color(VERY_DARK_BLUE)
+ }
+ Border::Dimension(_) => look::BorderStyle::none().with_color(GRAY),
+ Border::Category(RowColBorder(HeadingRegion::Rows, Axis2::X)) => {
+ look::BorderStyle::solid().with_color(GRAY)
+ }
+ Border::Category(RowColBorder(HeadingRegion::Columns, Axis2::Y)) => {
+ look::BorderStyle::solid().with_color(LIGHT_GRAY)
+ }
+ Border::Category(_) => look::BorderStyle::none().with_color(GRAY),
+ Border::DataLeft => look::BorderStyle::none().with_color(VERY_DARK_BLUE),
+ Border::DataTop => look::BorderStyle::solid().with_color(VERY_DARK_BLUE),
+ }
+ }
}
#[derive(Clone, Debug, Deserialize, Default)]
pub color: Option<Color>,
}
-#[derive(Clone, Debug, Default, Deserialize)]
+#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", default)]
struct PrintingProperties {
#[serde(rename = "@printAllLayers")]
continuation_text_at_top: bool,
}
+impl Default for PrintingProperties {
+ fn default() -> Self {
+ Self {
+ print_all_layers: false,
+ print_each_layer_on_separate_page: false,
+ rescale_wide_table_to_fit_page: false,
+ rescale_long_table_to_fit_page: false,
+ window_orphan_lines: 2,
+ continuation_text: String::from("(cont.)"),
+ continuation_text_at_bottom: false,
+ continuation_text_at_top: false,
+ }
+ }
+}
+
/// A length.
#[derive(Copy, Clone, Default, PartialEq)]
pub struct Length(
paginate_layers: false,
shrink_to_fit: EnumMap::from_fn(|_| false),
show_continuations: [false, false],
- continuation: None,
+ continuation: Some(String::from("(cont.)")),
n_orphan_lines: 5,
};
assert_eq!(&look, &expected);