use std::collections::BTreeMap;
use crate::annotation::{NoteKind, RevisionKind};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct DocModel {
pub blocks: Vec<Block>,
pub regions: Vec<SourceRegion>,
pub meta: DocMeta,
pub custom_properties: BTreeMap<String, String>,
pub custom_xml_items: Vec<CustomXmlItem>,
pub setup: DocSetup,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CustomXmlItem {
pub store_item_id: String,
pub xml: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct WebExtensionTaskPane {
pub extension_id: String,
pub reference_id: String,
pub version: String,
pub store: String,
pub store_type: String,
pub properties: BTreeMap<String, String>,
pub dock_state: String,
pub visible: bool,
pub width: u32,
pub row: u32,
pub locked: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SourceRegionKind {
Main,
Footnote,
HeaderFooter,
Annotation,
Endnote,
TextBox,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceRegion {
pub kind: SourceRegionKind,
pub source_story_index: Option<usize>,
pub block_start: usize,
pub block_end: usize,
pub source_start_cp: usize,
pub source_len_cp: usize,
pub text_start: usize,
pub text_len: usize,
}
impl DocModel {
pub fn source_regions(&self, kind: SourceRegionKind) -> impl Iterator<Item = &SourceRegion> {
self.regions
.iter()
.filter(move |region| region.kind == kind)
}
pub fn source_region_blocks(&self, region: &SourceRegion) -> &[Block] {
let start = region.block_start.min(self.blocks.len());
let end = region.block_end.min(self.blocks.len());
if start <= end {
&self.blocks[start..end]
} else {
&self.blocks[0..0]
}
}
pub fn source_region_text(&self, region: &SourceRegion) -> String {
let mut out = String::new();
append_blocks_text(self.source_region_blocks(region), &mut out);
out
}
pub fn source_region_kind_text(&self, kind: SourceRegionKind) -> String {
let mut out = String::new();
for region in self.source_regions(kind) {
append_blocks_text(self.source_region_blocks(region), &mut out);
}
out
}
}
fn append_blocks_text(blocks: &[Block], out: &mut String) {
for block in blocks {
match block {
Block::Paragraph(paragraph) => {
for run in ¶graph.runs {
out.push_str(&run.text);
}
}
Block::Table(table) => {
for row in &table.rows {
for cell in &row.cells {
append_blocks_text(&cell.blocks, out);
}
}
}
Block::Image(_) | Block::Chart(_) | Block::SectionBreak(_) => {}
Block::PageBreak => out.push('\n'),
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct DocMeta {
pub codepage: u16,
pub lid: u16,
pub stats: Stats,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Stats {
pub paragraphs: u32,
pub tables: u16,
pub figures: u16,
pub text_chars: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Block {
Paragraph(Paragraph),
Table(Table),
Image(Image),
Chart(Chart),
PageBreak,
SectionBreak(SectionSetup),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionBreakKind {
NextPage,
EvenPage,
OddPage,
}
#[cfg(feature = "docx")]
impl SectionBreakKind {
pub(crate) fn wml_value(self) -> &'static str {
match self {
SectionBreakKind::NextPage => "nextPage",
SectionBreakKind::EvenPage => "evenPage",
SectionBreakKind::OddPage => "oddPage",
}
}
pub(crate) fn from_wml_value(value: &str) -> Option<Self> {
let value = value.trim();
match value {
"nextPage" => Some(SectionBreakKind::NextPage),
"evenPage" => Some(SectionBreakKind::EvenPage),
"oddPage" => Some(SectionBreakKind::OddPage),
_ => None,
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Paragraph {
pub props: ParaProps,
pub runs: Vec<Run>,
}
impl Paragraph {
pub fn text(&self) -> String {
self.runs.iter().map(|r| r.text.as_str()).collect()
}
pub fn is_blank(&self) -> bool {
self.runs
.iter()
.all(|r| r.text.trim().is_empty() && r.image.is_none())
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct Spacing {
pub before_pt: Option<f32>,
pub after_pt: Option<f32>,
pub line_pct: Option<f32>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct Indent {
pub left_pt: Option<f32>,
pub right_pt: Option<f32>,
pub first_line_pt: Option<f32>,
pub hanging_pt: Option<f32>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ParaProps {
pub style_id: Option<String>,
pub style_name: Option<String>,
pub heading_level: Option<u8>,
pub align: Align,
pub outline_level: Option<u8>,
pub list: Option<ListInfo>,
pub spacing: Spacing,
pub indent: Indent,
pub shading: Option<Color>,
pub page_break_before: bool,
pub bidi: bool,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ParagraphStyle {
pub id: String,
pub name: String,
pub based_on: Option<String>,
pub next: Option<String>,
pub q_format: bool,
pub heading_level: Option<u8>,
pub align: Align,
pub spacing: Spacing,
pub indent: Indent,
pub shading: Option<Color>,
pub run: CharProps,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AuthoredComment {
pub text: String,
pub author: Option<String>,
pub initials: Option<String>,
pub date: Option<String>,
pub parent_comment_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuthoredRevision {
pub kind: RevisionKind,
pub author: Option<String>,
pub date: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct AuthoredContentControl {
pub alias: Option<String>,
pub tag: Option<String>,
pub data_binding_xpath: Option<String>,
pub data_binding_store_item_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuthoredNote {
pub kind: NoteKind,
pub text: String,
}
impl Default for AuthoredRevision {
fn default() -> Self {
Self {
kind: RevisionKind::Insertion,
author: None,
date: None,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ListInfo {
pub level: u8,
pub ordered: bool,
pub label: String,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Run {
pub text: String,
pub props: CharProps,
pub field: FieldRole,
pub field_dirty: bool,
pub field_unsupported_reason: Option<FieldUnsupportedReason>,
pub image: Option<Image>,
pub comment: Option<AuthoredComment>,
pub revision: Option<AuthoredRevision>,
pub content_control: Option<AuthoredContentControl>,
pub bookmark: Option<String>,
pub note: Option<AuthoredNote>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldUnsupportedReason {
UnresolvedBookmark,
UnsupportedSwitch,
NoComputedResult,
}
pub(crate) fn referenceable_bookmark_name(name: &str) -> bool {
!name.trim().is_empty()
&& !name.starts_with('\\')
&& !name.contains('"')
&& !name.chars().any(char::is_whitespace)
}
pub(crate) fn normalize_field_instruction(instruction: &str) -> String {
instruction.split_whitespace().collect::<Vec<_>>().join(" ")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl Color {
pub fn rgb(r: u8, g: u8, b: u8) -> Self {
Color { r, g, b }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TableBorderSide {
Top,
Left,
Bottom,
Right,
InsideHorizontal,
InsideVertical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TableBorderStyle {
Single,
Dotted,
Dashed,
Double,
}
#[cfg(feature = "docx")]
impl TableBorderStyle {
pub(crate) fn wml_value(self) -> &'static str {
match self {
TableBorderStyle::Single => "single",
TableBorderStyle::Dotted => "dotted",
TableBorderStyle::Dashed => "dashed",
TableBorderStyle::Double => "double",
}
}
pub(crate) fn from_wml_value(value: &str) -> Option<Self> {
let value = value.trim();
match value {
"single" => Some(TableBorderStyle::Single),
"dotted" => Some(TableBorderStyle::Dotted),
"dashed" => Some(TableBorderStyle::Dashed),
"double" => Some(TableBorderStyle::Double),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct TableBorderColors {
pub top: Option<Color>,
pub left: Option<Color>,
pub bottom: Option<Color>,
pub right: Option<Color>,
pub inside_h: Option<Color>,
pub inside_v: Option<Color>,
}
impl TableBorderColors {
pub fn get(&self, side: TableBorderSide) -> Option<Color> {
match side {
TableBorderSide::Top => self.top,
TableBorderSide::Left => self.left,
TableBorderSide::Bottom => self.bottom,
TableBorderSide::Right => self.right,
TableBorderSide::InsideHorizontal => self.inside_h,
TableBorderSide::InsideVertical => self.inside_v,
}
}
pub fn set(&mut self, side: TableBorderSide, color: Color) {
match side {
TableBorderSide::Top => self.top = Some(color),
TableBorderSide::Left => self.left = Some(color),
TableBorderSide::Bottom => self.bottom = Some(color),
TableBorderSide::Right => self.right = Some(color),
TableBorderSide::InsideHorizontal => self.inside_h = Some(color),
TableBorderSide::InsideVertical => self.inside_v = Some(color),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct TableBorderSizes {
pub top: Option<u16>,
pub left: Option<u16>,
pub bottom: Option<u16>,
pub right: Option<u16>,
pub inside_h: Option<u16>,
pub inside_v: Option<u16>,
}
impl TableBorderSizes {
pub fn get(&self, side: TableBorderSide) -> Option<u16> {
match side {
TableBorderSide::Top => self.top,
TableBorderSide::Left => self.left,
TableBorderSide::Bottom => self.bottom,
TableBorderSide::Right => self.right,
TableBorderSide::InsideHorizontal => self.inside_h,
TableBorderSide::InsideVertical => self.inside_v,
}
}
pub fn set(&mut self, side: TableBorderSide, size: u16) {
let size = size.max(1);
match side {
TableBorderSide::Top => self.top = Some(size),
TableBorderSide::Left => self.left = Some(size),
TableBorderSide::Bottom => self.bottom = Some(size),
TableBorderSide::Right => self.right = Some(size),
TableBorderSide::InsideHorizontal => self.inside_h = Some(size),
TableBorderSide::InsideVertical => self.inside_v = Some(size),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct TableBorderStyles {
pub top: Option<TableBorderStyle>,
pub left: Option<TableBorderStyle>,
pub bottom: Option<TableBorderStyle>,
pub right: Option<TableBorderStyle>,
pub inside_h: Option<TableBorderStyle>,
pub inside_v: Option<TableBorderStyle>,
}
impl TableBorderStyles {
pub fn get(&self, side: TableBorderSide) -> Option<TableBorderStyle> {
match side {
TableBorderSide::Top => self.top,
TableBorderSide::Left => self.left,
TableBorderSide::Bottom => self.bottom,
TableBorderSide::Right => self.right,
TableBorderSide::InsideHorizontal => self.inside_h,
TableBorderSide::InsideVertical => self.inside_v,
}
}
pub fn set(&mut self, side: TableBorderSide, style: TableBorderStyle) {
match side {
TableBorderSide::Top => self.top = Some(style),
TableBorderSide::Left => self.left = Some(style),
TableBorderSide::Bottom => self.bottom = Some(style),
TableBorderSide::Right => self.right = Some(style),
TableBorderSide::InsideHorizontal => self.inside_h = Some(style),
TableBorderSide::InsideVertical => self.inside_v = Some(style),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VertAlign {
#[default]
Baseline,
Super,
Sub,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CharProps {
pub bold: bool,
pub italic: bool,
pub underline: bool,
pub strike: bool,
pub hidden: bool,
pub font: Option<String>,
pub size_half_pt: Option<u16>,
pub color: Option<Color>,
pub highlight: Option<String>,
pub vert_align: VertAlign,
pub small_caps: bool,
pub caps: bool,
pub rtl: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum FieldRole {
#[default]
None,
Hyperlink {
url: String,
},
Simple {
instruction: String,
},
Other,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Align {
#[default]
Left,
Center,
Right,
Justify,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Table {
pub rows: Vec<Row>,
pub header_rows: usize,
pub col_widths_pct: Vec<f32>,
pub width_pct: Option<f32>,
pub fixed_layout: bool,
pub indent_twips: Option<i32>,
pub align: Option<Align>,
pub bidi_visual: bool,
pub border_color: Option<Color>,
pub border_colors: TableBorderColors,
pub border_size_eighths: Option<u16>,
pub border_sizes: TableBorderSizes,
pub border_style: Option<TableBorderStyle>,
pub border_styles: TableBorderStyles,
}
impl Table {
pub fn is_simple_grid(&self) -> bool {
self.rows.iter().all(|row| {
row.cells.iter().all(|c| {
c.row_span <= 1
&& c.col_span <= 1
&& c.blocks.len() <= 1
&& c.blocks.iter().all(|b| match b {
Block::Paragraph(p) => !p.text().contains('\n'),
_ => false,
})
})
})
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Row {
pub cells: Vec<Cell>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum VCell {
#[default]
Top,
Center,
Bottom,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CellMargins {
pub top: u32,
pub right: u32,
pub bottom: u32,
pub left: u32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Cell {
pub blocks: Vec<Block>,
pub row_span: u16,
pub col_span: u16,
pub is_header: bool,
pub shading: Option<Color>,
pub valign: VCell,
pub width_pct: Option<f32>,
pub margins: Option<CellMargins>,
}
impl Default for Cell {
fn default() -> Self {
Cell {
blocks: Vec::new(),
row_span: 1,
col_span: 1,
is_header: false,
shading: None,
valign: VCell::Top,
width_pct: None,
margins: None,
}
}
}
impl Cell {
pub fn text(&self) -> String {
self.blocks
.iter()
.filter_map(|b| match b {
Block::Paragraph(p) => Some(p.text()),
_ => None,
})
.collect::<Vec<_>>()
.join("\n")
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Image {
pub alt: Option<String>,
pub bytes: Option<Vec<u8>>,
pub mime: Option<String>,
pub width_px: Option<u32>,
pub height_px: Option<u32>,
pub rotation_degrees: Option<i32>,
pub floating_offset_emu: Option<(i64, i64)>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ChartKind {
#[default]
Bar,
StackedBar,
PercentStackedBar,
Bar3D,
StackedBar3D,
PercentStackedBar3D,
Column,
StackedColumn,
PercentStackedColumn,
Column3D,
StackedColumn3D,
PercentStackedColumn3D,
Line,
LineNoMarkers,
SmoothLine,
StackedLine,
PercentStackedLine,
Line3D,
Area,
StackedArea,
PercentStackedArea,
Area3D,
StackedArea3D,
PercentStackedArea3D,
Radar,
RadarWithMarkers,
FilledRadar,
Scatter,
ScatterMarkers,
ScatterLines,
ScatterSmooth,
ScatterSmoothNoMarkers,
Bubble,
Bubble3D,
Pie,
ExplodedPie,
Pie3D,
ExplodedPie3D,
PieOfPie,
BarOfPie,
Doughnut,
ExplodedDoughnut,
Surface,
Surface3D,
StockHighLowClose,
Stock,
Waterfall,
Treemap,
Sunburst,
Histogram,
BoxWhisker,
Funnel,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ChartShape {
#[default]
Box,
Cylinder,
Cone,
ConeToMax,
Pyramid,
PyramidToMax,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ChartSeries {
pub name: String,
pub values: Vec<f64>,
pub bubble_sizes: Vec<f64>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Chart {
pub kind: ChartKind,
pub title: Option<String>,
pub categories: Vec<String>,
pub series: Vec<ChartSeries>,
pub width_px: Option<u32>,
pub height_px: Option<u32>,
pub alt: Option<String>,
pub wireframe: bool,
pub shape: ChartShape,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PageSetup {
pub width_pt: f32,
pub height_pt: f32,
pub margin_pt: f32,
pub margin_left_pt: Option<f32>,
pub margin_right_pt: Option<f32>,
pub margin_top_pt: Option<f32>,
pub margin_bottom_pt: Option<f32>,
pub landscape: bool,
}
impl PageSetup {
pub fn left(&self) -> f32 {
self.margin_left_pt.unwrap_or(self.margin_pt)
}
pub fn right(&self) -> f32 {
self.margin_right_pt.unwrap_or(self.margin_pt)
}
pub fn top(&self) -> f32 {
self.margin_top_pt.unwrap_or(self.margin_pt)
}
pub fn bottom(&self) -> f32 {
self.margin_bottom_pt.unwrap_or(self.margin_pt)
}
}
impl Default for PageSetup {
fn default() -> Self {
PageSetup {
width_pt: 595.3,
height_pt: 841.9,
margin_pt: 72.0,
margin_left_pt: None,
margin_right_pt: None,
margin_top_pt: None,
margin_bottom_pt: None,
landscape: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PageNumberFormat {
Decimal,
DecimalZero,
NumberInDash,
DecimalFullWidth,
DecimalHalfWidth,
DecimalFullWidth2,
DecimalEnclosedCircle,
DecimalEnclosedFullstop,
DecimalEnclosedParen,
Ganada,
Chosung,
KoreanDigital,
KoreanCounting,
KoreanLegal,
KoreanDigital2,
LowerLetter,
UpperLetter,
LowerRoman,
UpperRoman,
Ordinal,
CardinalText,
OrdinalText,
}
#[cfg(feature = "docx")]
impl PageNumberFormat {
pub(crate) fn wml_value(self) -> &'static str {
match self {
PageNumberFormat::Decimal => "decimal",
PageNumberFormat::DecimalZero => "decimalZero",
PageNumberFormat::NumberInDash => "numberInDash",
PageNumberFormat::DecimalFullWidth => "decimalFullWidth",
PageNumberFormat::DecimalHalfWidth => "decimalHalfWidth",
PageNumberFormat::DecimalFullWidth2 => "decimalFullWidth2",
PageNumberFormat::DecimalEnclosedCircle => "decimalEnclosedCircle",
PageNumberFormat::DecimalEnclosedFullstop => "decimalEnclosedFullstop",
PageNumberFormat::DecimalEnclosedParen => "decimalEnclosedParen",
PageNumberFormat::Ganada => "ganada",
PageNumberFormat::Chosung => "chosung",
PageNumberFormat::KoreanDigital => "koreanDigital",
PageNumberFormat::KoreanCounting => "koreanCounting",
PageNumberFormat::KoreanLegal => "koreanLegal",
PageNumberFormat::KoreanDigital2 => "koreanDigital2",
PageNumberFormat::LowerLetter => "lowerLetter",
PageNumberFormat::UpperLetter => "upperLetter",
PageNumberFormat::LowerRoman => "lowerRoman",
PageNumberFormat::UpperRoman => "upperRoman",
PageNumberFormat::Ordinal => "ordinal",
PageNumberFormat::CardinalText => "cardinalText",
PageNumberFormat::OrdinalText => "ordinalText",
}
}
pub(crate) fn from_wml_value(value: &str) -> Option<Self> {
let value = value.trim();
match value {
"decimal" => Some(PageNumberFormat::Decimal),
"decimalZero" => Some(PageNumberFormat::DecimalZero),
"numberInDash" => Some(PageNumberFormat::NumberInDash),
"decimalFullWidth" => Some(PageNumberFormat::DecimalFullWidth),
"decimalHalfWidth" => Some(PageNumberFormat::DecimalHalfWidth),
"decimalFullWidth2" => Some(PageNumberFormat::DecimalFullWidth2),
"decimalEnclosedCircle" => Some(PageNumberFormat::DecimalEnclosedCircle),
"decimalEnclosedFullstop" => Some(PageNumberFormat::DecimalEnclosedFullstop),
"decimalEnclosedParen" => Some(PageNumberFormat::DecimalEnclosedParen),
"ganada" => Some(PageNumberFormat::Ganada),
"chosung" => Some(PageNumberFormat::Chosung),
"koreanDigital" => Some(PageNumberFormat::KoreanDigital),
"koreanCounting" => Some(PageNumberFormat::KoreanCounting),
"koreanLegal" => Some(PageNumberFormat::KoreanLegal),
"koreanDigital2" => Some(PageNumberFormat::KoreanDigital2),
"lowerLetter" => Some(PageNumberFormat::LowerLetter),
"upperLetter" => Some(PageNumberFormat::UpperLetter),
"lowerRoman" => Some(PageNumberFormat::LowerRoman),
"upperRoman" => Some(PageNumberFormat::UpperRoman),
"ordinal" => Some(PageNumberFormat::Ordinal),
"cardinalText" => Some(PageNumberFormat::CardinalText),
"ordinalText" => Some(PageNumberFormat::OrdinalText),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextDirection {
LeftToRightTopToBottom,
TopToBottomRightToLeft,
BottomToTopLeftToRight,
LeftToRightTopToBottomVertical,
TopToBottomRightToLeftVertical,
TopToBottomLeftToRightVertical,
}
#[cfg(feature = "docx")]
impl TextDirection {
pub(crate) fn wml_value(self) -> &'static str {
match self {
TextDirection::LeftToRightTopToBottom => "lrTb",
TextDirection::TopToBottomRightToLeft => "tbRl",
TextDirection::BottomToTopLeftToRight => "btLr",
TextDirection::LeftToRightTopToBottomVertical => "lrTbV",
TextDirection::TopToBottomRightToLeftVertical => "tbRlV",
TextDirection::TopToBottomLeftToRightVertical => "tbLrV",
}
}
pub(crate) fn from_wml_value(value: &str) -> Option<Self> {
let value = value.trim();
match value {
"lrTb" => Some(TextDirection::LeftToRightTopToBottom),
"tbRl" => Some(TextDirection::TopToBottomRightToLeft),
"btLr" => Some(TextDirection::BottomToTopLeftToRight),
"lrTbV" => Some(TextDirection::LeftToRightTopToBottomVertical),
"tbRlV" => Some(TextDirection::TopToBottomRightToLeftVertical),
"tbLrV" => Some(TextDirection::TopToBottomLeftToRightVertical),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DocGridType {
Default,
Lines,
LinesAndChars,
SnapToChars,
}
#[cfg(feature = "docx")]
impl DocGridType {
pub(crate) fn wml_value(self) -> &'static str {
match self {
DocGridType::Default => "default",
DocGridType::Lines => "lines",
DocGridType::LinesAndChars => "linesAndChars",
DocGridType::SnapToChars => "snapToChars",
}
}
pub(crate) fn from_wml_value(value: &str) -> Option<Self> {
let value = value.trim();
match value {
"default" => Some(DocGridType::Default),
"lines" => Some(DocGridType::Lines),
"linesAndChars" => Some(DocGridType::LinesAndChars),
"snapToChars" => Some(DocGridType::SnapToChars),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DocGrid {
pub grid_type: DocGridType,
pub line_pitch: Option<u32>,
pub character_space: Option<u32>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct SectionSetup {
pub section_break: Option<SectionBreakKind>,
pub page: PageSetup,
pub header: Vec<Block>,
pub first_header: Vec<Block>,
pub even_header: Vec<Block>,
pub footer: Vec<Block>,
pub first_footer: Vec<Block>,
pub even_footer: Vec<Block>,
pub title_page: bool,
pub page_numbers: bool,
pub page_number_start: Option<u32>,
pub page_number_format: Option<PageNumberFormat>,
pub columns: Option<u16>,
pub text_direction: Option<TextDirection>,
pub doc_grid: Option<DocGrid>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct DocSetup {
pub page: PageSetup,
pub styles: Vec<ParagraphStyle>,
pub header: Vec<Block>,
pub first_header: Vec<Block>,
pub even_header: Vec<Block>,
pub footer: Vec<Block>,
pub first_footer: Vec<Block>,
pub even_footer: Vec<Block>,
pub title_page: bool,
pub page_numbers: bool,
pub page_number_start: Option<u32>,
pub page_number_format: Option<PageNumberFormat>,
pub columns: Option<u16>,
pub text_direction: Option<TextDirection>,
pub doc_grid: Option<DocGrid>,
pub document_id: Option<String>,
pub web_extension_task_panes: Vec<WebExtensionTaskPane>,
pub title: Option<String>,
pub subject: Option<String>,
pub creator: Option<String>,
pub description: Option<String>,
pub keywords: Option<String>,
pub category: Option<String>,
pub content_status: Option<String>,
pub last_modified_by: Option<String>,
pub created: Option<String>,
pub modified: Option<String>,
pub last_printed: Option<String>,
pub revision: Option<String>,
pub version: Option<String>,
}
impl From<&DocSetup> for SectionSetup {
fn from(setup: &DocSetup) -> Self {
SectionSetup {
section_break: None,
page: setup.page,
header: setup.header.clone(),
first_header: setup.first_header.clone(),
even_header: setup.even_header.clone(),
footer: setup.footer.clone(),
first_footer: setup.first_footer.clone(),
even_footer: setup.even_footer.clone(),
title_page: setup.title_page,
page_numbers: setup.page_numbers,
page_number_start: setup.page_number_start,
page_number_format: setup.page_number_format,
columns: setup.columns,
text_direction: setup.text_direction,
doc_grid: setup.doc_grid,
}
}
}
#[cfg(all(test, feature = "docx"))]
mod tests {
use super::{DocGridType, PageNumberFormat, SectionBreakKind, TableBorderStyle, TextDirection};
#[test]
fn wml_enum_parsers_trim_ooxml_values() {
assert_eq!(
SectionBreakKind::from_wml_value(" evenPage "),
Some(SectionBreakKind::EvenPage)
);
assert_eq!(
TableBorderStyle::from_wml_value(" dotted "),
Some(TableBorderStyle::Dotted)
);
assert_eq!(
PageNumberFormat::from_wml_value(" lowerRoman "),
Some(PageNumberFormat::LowerRoman)
);
assert_eq!(
TextDirection::from_wml_value(" tbRl "),
Some(TextDirection::TopToBottomRightToLeft)
);
assert_eq!(
DocGridType::from_wml_value(" linesAndChars "),
Some(DocGridType::LinesAndChars)
);
}
}