use crate::model::{
normalize_field_instruction, referenceable_bookmark_name, Align, AuthoredComment,
AuthoredContentControl, AuthoredNote, AuthoredRevision, Block, Cell, CharProps, Chart,
ChartKind, ChartSeries, ChartShape, Color, CustomXmlItem, DocGrid, DocGridType, DocModel,
FieldRole, Image, ListInfo, PageNumberFormat, PageSetup, ParaProps, Paragraph, ParagraphStyle,
Row, Run, SectionBreakKind, SectionSetup, Table, TableBorderSide, TableBorderStyle,
TextDirection, VCell, WebExtensionTaskPane,
};
use crate::{NoteKind, RevisionKind};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct RunBuilder {
run: Run,
}
impl RunBuilder {
pub fn new(text: impl Into<String>) -> Self {
Self {
run: Run {
text: text.into(),
..Run::default()
},
}
}
pub fn bold(mut self) -> Self {
self.run.props.bold = true;
self
}
pub fn italic(mut self) -> Self {
self.run.props.italic = true;
self
}
pub fn underline(mut self) -> Self {
self.run.props.underline = true;
self
}
pub fn small_caps(mut self) -> Self {
self.run.props.small_caps = true;
self
}
pub fn caps(mut self) -> Self {
self.run.props.caps = true;
self
}
pub fn rtl(mut self) -> Self {
self.run.props.rtl = true;
self
}
pub fn font(mut self, font: impl Into<String>) -> Self {
let font = font.into().trim().to_string();
self.run.props.font = (!font.is_empty()).then_some(font);
self
}
pub fn size_half_pt(mut self, size: u16) -> Self {
self.run.props.size_half_pt = Some(size);
self
}
pub fn color(mut self, color: Color) -> Self {
self.run.props.color = Some(color);
self
}
pub fn highlight(mut self, highlight: impl Into<String>) -> Self {
let highlight = highlight.into().trim().to_string();
self.run.props.highlight = (!highlight.is_empty()).then_some(highlight);
self
}
pub fn field(mut self, instruction: impl Into<String>) -> Self {
let instruction = normalize_field_instruction(&instruction.into());
if !instruction.is_empty() {
self.run.field = FieldRole::Simple { instruction };
}
self
}
pub fn page_ref(self, bookmark: impl Into<String>) -> Self {
let bookmark = bookmark.into().trim().to_string();
if referenceable_bookmark_name(&bookmark) {
self.field(format!("PAGEREF {bookmark} \\h"))
} else {
self
}
}
pub fn hyperlink(mut self, url: impl Into<String>) -> Self {
self.run.field = hyperlink_field(url.into());
self
}
pub fn comment<C>(mut self, comment: C) -> Self
where
C: Into<AuthoredComment>,
{
self.run.comment = Some(comment.into());
self
}
pub fn revision<R>(mut self, revision: R) -> Self
where
R: Into<AuthoredRevision>,
{
self.run.revision = Some(revision.into());
self
}
pub fn content_control<C>(mut self, control: C) -> Self
where
C: Into<AuthoredContentControl>,
{
self.run.content_control = Some(control.into());
self
}
pub fn bookmark(mut self, name: impl Into<String>) -> Self {
let name = name.into().trim().to_string();
if referenceable_bookmark_name(&name) {
self.run.bookmark = Some(name);
}
self
}
pub fn footnote(mut self, text: impl Into<String>) -> Self {
self.run.note = Some(AuthoredNote {
kind: NoteKind::Footnote,
text: text.into(),
});
self
}
pub fn endnote(mut self, text: impl Into<String>) -> Self {
self.run.note = Some(AuthoredNote {
kind: NoteKind::Endnote,
text: text.into(),
});
self
}
pub fn build(self) -> Run {
self.run
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ParagraphBuilder {
paragraph: Paragraph,
}
impl ParagraphBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn text(text: impl Into<String>) -> Self {
Self::new().push_run(plain_run(text.into(), CharProps::default()))
}
pub fn runs<I>(mut self, runs: I) -> Self
where
I: IntoIterator<Item = Run>,
{
self.paragraph.runs = runs.into_iter().collect();
self
}
pub fn push_run(mut self, run: Run) -> Self {
self.paragraph.runs.push(run);
self
}
pub fn heading_level(mut self, level: u8) -> Self {
self.paragraph.props.heading_level = Some(level.clamp(1, 6));
self
}
pub fn style(mut self, style_id: impl Into<String>) -> Self {
let style_id = style_id.into().trim().to_string();
self.paragraph.props.style_id = (!style_id.is_empty()).then_some(style_id);
self
}
pub fn align(mut self, align: Align) -> Self {
self.paragraph.props.align = align;
self
}
pub fn spacing_before_pt(mut self, before_pt: f32) -> Self {
self.paragraph.props.spacing.before_pt = Some(before_pt);
self
}
pub fn spacing_after_pt(mut self, after_pt: f32) -> Self {
self.paragraph.props.spacing.after_pt = Some(after_pt);
self
}
pub fn line_pct(mut self, line_pct: f32) -> Self {
self.paragraph.props.spacing.line_pct = Some(line_pct);
self
}
pub fn indent_left_pt(mut self, left_pt: f32) -> Self {
self.paragraph.props.indent.left_pt = Some(left_pt);
self
}
pub fn indent_right_pt(mut self, right_pt: f32) -> Self {
self.paragraph.props.indent.right_pt = Some(right_pt);
self
}
pub fn first_line_pt(mut self, first_line_pt: f32) -> Self {
self.paragraph.props.indent.first_line_pt = Some(first_line_pt);
self
}
pub fn hanging_pt(mut self, hanging_pt: f32) -> Self {
self.paragraph.props.indent.hanging_pt = Some(hanging_pt);
self
}
pub fn shading(mut self, color: Color) -> Self {
self.paragraph.props.shading = Some(color);
self
}
pub fn page_break_before(mut self) -> Self {
self.paragraph.props.page_break_before = true;
self
}
pub fn bidi(mut self) -> Self {
self.paragraph.props.bidi = true;
self
}
pub fn build(self) -> Paragraph {
self.paragraph
}
}
impl From<ParagraphBuilder> for Paragraph {
fn from(builder: ParagraphBuilder) -> Self {
builder.build()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ParagraphStyleBuilder {
style: ParagraphStyle,
}
impl ParagraphStyleBuilder {
pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
style: ParagraphStyle {
id: id.into().trim().to_string(),
name: name.into().trim().to_string(),
..ParagraphStyle::default()
},
}
}
pub fn from_style(style: ParagraphStyle) -> Self {
Self { style }
}
pub fn based_on(mut self, style_id: impl Into<String>) -> Self {
let style_id = style_id.into().trim().to_string();
self.style.based_on = (!style_id.is_empty()).then_some(style_id);
self
}
pub fn next(mut self, style_id: impl Into<String>) -> Self {
let style_id = style_id.into().trim().to_string();
self.style.next = (!style_id.is_empty()).then_some(style_id);
self
}
pub fn q_format(mut self) -> Self {
self.style.q_format = true;
self
}
pub fn heading_level(mut self, level: u8) -> Self {
self.style.heading_level = Some(level.clamp(1, 9));
self
}
pub fn align(mut self, align: Align) -> Self {
self.style.align = align;
self
}
pub fn spacing_before_pt(mut self, before_pt: f32) -> Self {
self.style.spacing.before_pt = Some(before_pt);
self
}
pub fn spacing_after_pt(mut self, after_pt: f32) -> Self {
self.style.spacing.after_pt = Some(after_pt);
self
}
pub fn line_pct(mut self, line_pct: f32) -> Self {
self.style.spacing.line_pct = Some(line_pct);
self
}
pub fn indent_left_pt(mut self, left_pt: f32) -> Self {
self.style.indent.left_pt = Some(left_pt);
self
}
pub fn indent_right_pt(mut self, right_pt: f32) -> Self {
self.style.indent.right_pt = Some(right_pt);
self
}
pub fn first_line_pt(mut self, first_line_pt: f32) -> Self {
self.style.indent.first_line_pt = Some(first_line_pt);
self
}
pub fn hanging_pt(mut self, hanging_pt: f32) -> Self {
self.style.indent.hanging_pt = Some(hanging_pt);
self
}
pub fn shading(mut self, color: Color) -> Self {
self.style.shading = Some(color);
self
}
pub fn run_bold(mut self) -> Self {
self.style.run.bold = true;
self
}
pub fn run_italic(mut self) -> Self {
self.style.run.italic = true;
self
}
pub fn run_underline(mut self) -> Self {
self.style.run.underline = true;
self
}
pub fn run_font(mut self, font: impl Into<String>) -> Self {
let font = font.into().trim().to_string();
self.style.run.font = (!font.is_empty()).then_some(font);
self
}
pub fn run_size_half_pt(mut self, size: u16) -> Self {
self.style.run.size_half_pt = Some(size);
self
}
pub fn run_color(mut self, color: Color) -> Self {
self.style.run.color = Some(color);
self
}
pub fn run_highlight(mut self, highlight: impl Into<String>) -> Self {
let highlight = highlight.into().trim().to_string();
self.style.run.highlight = (!highlight.is_empty()).then_some(highlight);
self
}
pub fn build(self) -> ParagraphStyle {
self.style
}
}
impl From<ParagraphStyleBuilder> for ParagraphStyle {
fn from(builder: ParagraphStyleBuilder) -> Self {
builder.build()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CommentBuilder {
comment: AuthoredComment,
}
impl CommentBuilder {
pub fn new(text: impl Into<String>) -> Self {
Self {
comment: AuthoredComment {
text: text.into(),
..AuthoredComment::default()
},
}
}
pub fn from_comment(comment: AuthoredComment) -> Self {
Self { comment }
}
pub fn author(mut self, author: impl Into<String>) -> Self {
let author = author.into().trim().to_string();
self.comment.author = (!author.is_empty()).then_some(author);
self
}
pub fn initials(mut self, initials: impl Into<String>) -> Self {
let initials = initials.into().trim().to_string();
self.comment.initials = (!initials.is_empty()).then_some(initials);
self
}
pub fn date(mut self, date: impl Into<String>) -> Self {
let date = date.into().trim().to_string();
self.comment.date = (!date.is_empty()).then_some(date);
self
}
pub fn parent_comment_id(mut self, id: impl Into<String>) -> Self {
let id = id.into().trim().to_string();
self.comment.parent_comment_id = (!id.is_empty()).then_some(id);
self
}
pub fn build(self) -> AuthoredComment {
self.comment
}
}
impl From<CommentBuilder> for AuthoredComment {
fn from(builder: CommentBuilder) -> Self {
builder.build()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RevisionBuilder {
revision: AuthoredRevision,
}
impl RevisionBuilder {
pub fn insertion() -> Self {
Self {
revision: AuthoredRevision {
kind: RevisionKind::Insertion,
..AuthoredRevision::default()
},
}
}
pub fn deletion() -> Self {
Self {
revision: AuthoredRevision {
kind: RevisionKind::Deletion,
..AuthoredRevision::default()
},
}
}
pub fn from_revision(revision: AuthoredRevision) -> Self {
Self { revision }
}
pub fn author(mut self, author: impl Into<String>) -> Self {
let author = author.into().trim().to_string();
self.revision.author = (!author.is_empty()).then_some(author);
self
}
pub fn date(mut self, date: impl Into<String>) -> Self {
let date = date.into().trim().to_string();
self.revision.date = (!date.is_empty()).then_some(date);
self
}
pub fn build(self) -> AuthoredRevision {
self.revision
}
}
impl Default for RevisionBuilder {
fn default() -> Self {
Self::insertion()
}
}
impl From<RevisionBuilder> for AuthoredRevision {
fn from(builder: RevisionBuilder) -> Self {
builder.build()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ContentControlBuilder {
control: AuthoredContentControl,
}
impl ContentControlBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn from_content_control(control: AuthoredContentControl) -> Self {
Self { control }
}
pub fn alias(mut self, alias: impl Into<String>) -> Self {
let alias = alias.into().trim().to_string();
self.control.alias = (!alias.is_empty()).then_some(alias);
self
}
pub fn tag(mut self, tag: impl Into<String>) -> Self {
let tag = tag.into().trim().to_string();
self.control.tag = (!tag.is_empty()).then_some(tag);
self
}
pub fn data_binding(
mut self,
xpath: impl Into<String>,
store_item_id: impl Into<String>,
) -> Self {
let xpath = xpath.into().trim().to_string();
let store_item_id = store_item_id.into().trim().to_string();
if !xpath.is_empty() && !store_item_id.is_empty() {
self.control.data_binding_xpath = Some(xpath);
self.control.data_binding_store_item_id = Some(store_item_id);
} else {
self.control.data_binding_xpath = None;
self.control.data_binding_store_item_id = None;
}
self
}
pub fn build(self) -> AuthoredContentControl {
self.control
}
}
impl From<ContentControlBuilder> for AuthoredContentControl {
fn from(builder: ContentControlBuilder) -> Self {
builder.build()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CellBuilder {
cell: Cell,
}
impl CellBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn text(text: impl Into<String>) -> Self {
Self::new().paragraph(text)
}
pub fn blocks<I>(mut self, blocks: I) -> Self
where
I: IntoIterator<Item = Block>,
{
self.cell.blocks = blocks.into_iter().collect();
self
}
pub fn paragraph(mut self, text: impl Into<String>) -> Self {
self.cell.blocks.push(plain_paragraph(text.into()));
self
}
pub fn paragraph_runs<I>(mut self, runs: I) -> Self
where
I: IntoIterator<Item = Run>,
{
self.cell.blocks.push(paragraph(runs, ParaProps::default()));
self
}
pub fn rich_paragraph<P>(mut self, paragraph: P) -> Self
where
P: Into<Paragraph>,
{
self.cell.blocks.push(Block::Paragraph(paragraph.into()));
self
}
pub fn rich_table<T>(mut self, table: T) -> Self
where
T: Into<Table>,
{
self.cell.blocks.push(Block::Table(table.into()));
self
}
pub fn push_block(mut self, block: Block) -> Self {
self.cell.blocks.push(block);
self
}
pub fn header(mut self) -> Self {
self.cell.is_header = true;
self
}
pub fn col_span(mut self, span: u16) -> Self {
self.cell.col_span = span.max(1);
self
}
pub fn row_span(mut self, span: u16) -> Self {
self.cell.row_span = span.max(1);
self
}
pub fn shading(mut self, color: Color) -> Self {
self.cell.shading = Some(color);
self
}
pub fn valign(mut self, valign: VCell) -> Self {
self.cell.valign = valign;
self
}
pub fn width_pct(mut self, width_pct: f32) -> Self {
self.cell.width_pct = Some(width_pct);
self
}
pub fn margins_twips(mut self, top: u32, right: u32, bottom: u32, left: u32) -> Self {
self.cell.margins = Some(crate::model::CellMargins {
top,
right,
bottom,
left,
});
self
}
pub fn build(self) -> Cell {
self.cell
}
}
impl From<CellBuilder> for Cell {
fn from(builder: CellBuilder) -> Self {
builder.build()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ImageBuilder {
image: Image,
}
impl ImageBuilder {
pub fn new(bytes: Vec<u8>, mime: impl Into<String>) -> Self {
Self {
image: Image {
bytes: Some(bytes),
mime: Some(mime.into()),
..Image::default()
},
}
}
pub fn from_image(image: Image) -> Self {
Self { image }
}
pub fn alt(mut self, alt: impl Into<String>) -> Self {
let alt = alt.into().trim().to_string();
self.image.alt = (!alt.is_empty()).then_some(alt);
self
}
pub fn size_px(mut self, width_px: u32, height_px: u32) -> Self {
self.image.width_px = Some(width_px);
self.image.height_px = Some(height_px);
self
}
pub fn width_px(mut self, width_px: u32) -> Self {
self.image.width_px = Some(width_px);
self
}
pub fn height_px(mut self, height_px: u32) -> Self {
self.image.height_px = Some(height_px);
self
}
pub fn rotate_degrees(mut self, degrees: i32) -> Self {
self.image.rotation_degrees = Some(degrees.rem_euclid(360));
self
}
pub fn floating_offset_emu(mut self, x_emu: i64, y_emu: i64) -> Self {
self.image.floating_offset_emu = Some((x_emu, y_emu));
self
}
pub fn build(self) -> Image {
self.image
}
}
impl From<ImageBuilder> for Image {
fn from(builder: ImageBuilder) -> Self {
builder.build()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ChartBuilder {
chart: Chart,
}
impl ChartBuilder {
pub fn bar() -> Self {
Self {
chart: Chart {
kind: ChartKind::Bar,
..Chart::default()
},
}
}
pub fn stacked_bar() -> Self {
Self {
chart: Chart {
kind: ChartKind::StackedBar,
..Chart::default()
},
}
}
pub fn percent_stacked_bar() -> Self {
Self {
chart: Chart {
kind: ChartKind::PercentStackedBar,
..Chart::default()
},
}
}
pub fn bar_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::Bar3D,
..Chart::default()
},
}
}
pub fn stacked_bar_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::StackedBar3D,
..Chart::default()
},
}
}
pub fn percent_stacked_bar_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::PercentStackedBar3D,
..Chart::default()
},
}
}
pub fn column() -> Self {
Self {
chart: Chart {
kind: ChartKind::Column,
..Chart::default()
},
}
}
pub fn stacked_column() -> Self {
Self {
chart: Chart {
kind: ChartKind::StackedColumn,
..Chart::default()
},
}
}
pub fn percent_stacked_column() -> Self {
Self {
chart: Chart {
kind: ChartKind::PercentStackedColumn,
..Chart::default()
},
}
}
pub fn column_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::Column3D,
..Chart::default()
},
}
}
pub fn stacked_column_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::StackedColumn3D,
..Chart::default()
},
}
}
pub fn percent_stacked_column_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::PercentStackedColumn3D,
..Chart::default()
},
}
}
pub fn line() -> Self {
Self {
chart: Chart {
kind: ChartKind::Line,
..Chart::default()
},
}
}
pub fn line_no_markers() -> Self {
Self {
chart: Chart {
kind: ChartKind::LineNoMarkers,
..Chart::default()
},
}
}
pub fn smooth_line() -> Self {
Self {
chart: Chart {
kind: ChartKind::SmoothLine,
..Chart::default()
},
}
}
pub fn stacked_line() -> Self {
Self {
chart: Chart {
kind: ChartKind::StackedLine,
..Chart::default()
},
}
}
pub fn percent_stacked_line() -> Self {
Self {
chart: Chart {
kind: ChartKind::PercentStackedLine,
..Chart::default()
},
}
}
pub fn line_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::Line3D,
..Chart::default()
},
}
}
pub fn area() -> Self {
Self {
chart: Chart {
kind: ChartKind::Area,
..Chart::default()
},
}
}
pub fn stacked_area() -> Self {
Self {
chart: Chart {
kind: ChartKind::StackedArea,
..Chart::default()
},
}
}
pub fn percent_stacked_area() -> Self {
Self {
chart: Chart {
kind: ChartKind::PercentStackedArea,
..Chart::default()
},
}
}
pub fn area_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::Area3D,
..Chart::default()
},
}
}
pub fn stacked_area_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::StackedArea3D,
..Chart::default()
},
}
}
pub fn percent_stacked_area_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::PercentStackedArea3D,
..Chart::default()
},
}
}
pub fn radar() -> Self {
Self {
chart: Chart {
kind: ChartKind::Radar,
..Chart::default()
},
}
}
pub fn radar_with_markers() -> Self {
Self {
chart: Chart {
kind: ChartKind::RadarWithMarkers,
..Chart::default()
},
}
}
pub fn filled_radar() -> Self {
Self {
chart: Chart {
kind: ChartKind::FilledRadar,
..Chart::default()
},
}
}
pub fn scatter() -> Self {
Self {
chart: Chart {
kind: ChartKind::Scatter,
..Chart::default()
},
}
}
pub fn scatter_markers() -> Self {
Self {
chart: Chart {
kind: ChartKind::ScatterMarkers,
..Chart::default()
},
}
}
pub fn scatter_lines() -> Self {
Self {
chart: Chart {
kind: ChartKind::ScatterLines,
..Chart::default()
},
}
}
pub fn scatter_smooth() -> Self {
Self {
chart: Chart {
kind: ChartKind::ScatterSmooth,
..Chart::default()
},
}
}
pub fn scatter_smooth_no_markers() -> Self {
Self {
chart: Chart {
kind: ChartKind::ScatterSmoothNoMarkers,
..Chart::default()
},
}
}
pub fn bubble() -> Self {
Self {
chart: Chart {
kind: ChartKind::Bubble,
..Chart::default()
},
}
}
pub fn bubble_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::Bubble3D,
..Chart::default()
},
}
}
pub fn pie() -> Self {
Self {
chart: Chart {
kind: ChartKind::Pie,
..Chart::default()
},
}
}
pub fn exploded_pie() -> Self {
Self {
chart: Chart {
kind: ChartKind::ExplodedPie,
..Chart::default()
},
}
}
pub fn pie_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::Pie3D,
..Chart::default()
},
}
}
pub fn exploded_pie_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::ExplodedPie3D,
..Chart::default()
},
}
}
pub fn pie_of_pie() -> Self {
Self {
chart: Chart {
kind: ChartKind::PieOfPie,
..Chart::default()
},
}
}
pub fn bar_of_pie() -> Self {
Self {
chart: Chart {
kind: ChartKind::BarOfPie,
..Chart::default()
},
}
}
pub fn doughnut() -> Self {
Self {
chart: Chart {
kind: ChartKind::Doughnut,
..Chart::default()
},
}
}
pub fn exploded_doughnut() -> Self {
Self {
chart: Chart {
kind: ChartKind::ExplodedDoughnut,
..Chart::default()
},
}
}
pub fn surface() -> Self {
Self {
chart: Chart {
kind: ChartKind::Surface,
..Chart::default()
},
}
}
pub fn surface_3d() -> Self {
Self {
chart: Chart {
kind: ChartKind::Surface3D,
..Chart::default()
},
}
}
pub fn stock_high_low_close() -> Self {
Self {
chart: Chart {
kind: ChartKind::StockHighLowClose,
..Chart::default()
},
}
}
pub fn stock() -> Self {
Self {
chart: Chart {
kind: ChartKind::Stock,
..Chart::default()
},
}
}
pub fn waterfall() -> Self {
Self {
chart: Chart {
kind: ChartKind::Waterfall,
..Chart::default()
},
}
}
pub fn treemap() -> Self {
Self {
chart: Chart {
kind: ChartKind::Treemap,
..Chart::default()
},
}
}
pub fn sunburst() -> Self {
Self {
chart: Chart {
kind: ChartKind::Sunburst,
..Chart::default()
},
}
}
pub fn histogram() -> Self {
Self {
chart: Chart {
kind: ChartKind::Histogram,
..Chart::default()
},
}
}
pub fn box_whisker() -> Self {
Self {
chart: Chart {
kind: ChartKind::BoxWhisker,
..Chart::default()
},
}
}
pub fn funnel() -> Self {
Self {
chart: Chart {
kind: ChartKind::Funnel,
..Chart::default()
},
}
}
pub fn from_chart(chart: Chart) -> Self {
Self { chart }
}
pub fn title(mut self, title: impl Into<String>) -> Self {
let title = title.into().trim().to_string();
self.chart.title = (!title.is_empty()).then_some(title);
self
}
pub fn categories<I, S>(mut self, categories: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.chart.categories = categories.into_iter().map(Into::into).collect();
self
}
pub fn series<I, V>(mut self, name: impl Into<String>, values: I) -> Self
where
I: IntoIterator<Item = V>,
V: Into<f64>,
{
self.chart.series.push(ChartSeries {
name: name.into(),
values: values.into_iter().map(Into::into).collect(),
bubble_sizes: Vec::new(),
});
self
}
pub fn bubble_series<I, V, J, S>(
mut self,
name: impl Into<String>,
values: I,
bubble_sizes: J,
) -> Self
where
I: IntoIterator<Item = V>,
V: Into<f64>,
J: IntoIterator<Item = S>,
S: Into<f64>,
{
self.chart.series.push(ChartSeries {
name: name.into(),
values: values.into_iter().map(Into::into).collect(),
bubble_sizes: bubble_sizes.into_iter().map(Into::into).collect(),
});
self
}
pub fn size_px(mut self, width_px: u32, height_px: u32) -> Self {
self.chart.width_px = Some(width_px);
self.chart.height_px = Some(height_px);
self
}
pub fn width_px(mut self, width_px: u32) -> Self {
self.chart.width_px = Some(width_px);
self
}
pub fn height_px(mut self, height_px: u32) -> Self {
self.chart.height_px = Some(height_px);
self
}
pub fn alt(mut self, alt: impl Into<String>) -> Self {
let alt = alt.into().trim().to_string();
self.chart.alt = (!alt.is_empty()).then_some(alt);
self
}
pub fn wireframe(mut self) -> Self {
self.chart.wireframe = true;
self
}
pub fn shape(mut self, shape: ChartShape) -> Self {
self.chart.shape = shape;
self
}
pub fn build(self) -> Chart {
self.chart
}
}
impl From<ChartBuilder> for Chart {
fn from(builder: ChartBuilder) -> Self {
builder.build()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct TableBuilder {
table: Table,
}
impl TableBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn from_table(table: Table) -> Self {
Self { table }
}
pub fn header_rows(mut self, header_rows: usize) -> Self {
self.table.header_rows = header_rows;
self
}
pub fn col_widths_pct<I>(mut self, widths: I) -> Self
where
I: IntoIterator<Item = f32>,
{
self.table.col_widths_pct = widths.into_iter().collect();
self
}
pub fn width_pct(mut self, width_pct: f32) -> Self {
self.table.width_pct = Some(width_pct);
self
}
pub fn fixed_layout(mut self) -> Self {
self.table.fixed_layout = true;
self
}
pub fn bidi_visual(mut self) -> Self {
self.table.bidi_visual = true;
self
}
pub fn indent_twips(mut self, indent_twips: i32) -> Self {
self.table.indent_twips = Some(indent_twips);
self
}
pub fn align(mut self, align: Align) -> Self {
self.table.align = Some(align);
self
}
pub fn border_color(mut self, color: Color) -> Self {
self.table.border_color = Some(color);
self
}
pub fn border_side_color(mut self, side: TableBorderSide, color: Color) -> Self {
self.table.border_colors.set(side, color);
self
}
pub fn border_size_eighths(mut self, size: u16) -> Self {
self.table.border_size_eighths = Some(size.max(1));
self
}
pub fn border_side_size_eighths(mut self, side: TableBorderSide, size: u16) -> Self {
self.table.border_sizes.set(side, size);
self
}
pub fn border_style(mut self, style: TableBorderStyle) -> Self {
self.table.border_style = Some(style);
self
}
pub fn border_side_style(mut self, side: TableBorderSide, style: TableBorderStyle) -> Self {
self.table.border_styles.set(side, style);
self
}
pub fn row<I, C>(mut self, cells: I) -> Self
where
I: IntoIterator<Item = C>,
C: Into<Cell>,
{
self.table.rows.push(Row {
cells: cells.into_iter().map(Into::into).collect(),
});
self
}
pub fn push_row(mut self, row: Row) -> Self {
self.table.rows.push(row);
self
}
pub fn build(mut self) -> Table {
for row in self.table.rows.iter_mut().take(self.table.header_rows) {
for cell in &mut row.cells {
cell.is_header = true;
}
}
self.table
}
}
impl From<TableBuilder> for Table {
fn from(builder: TableBuilder) -> Self {
builder.build()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct DocBuilder {
model: DocModel,
}
impl DocBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn from_model(model: DocModel) -> Self {
Self { model }
}
pub fn title(mut self, title: impl Into<String>) -> Self {
let title = title.into().trim().to_string();
self.model.setup.title = (!title.is_empty()).then_some(title);
self
}
pub fn subject(mut self, subject: impl Into<String>) -> Self {
let subject = subject.into().trim().to_string();
self.model.setup.subject = (!subject.is_empty()).then_some(subject);
self
}
pub fn creator(mut self, creator: impl Into<String>) -> Self {
let creator = creator.into().trim().to_string();
self.model.setup.creator = (!creator.is_empty()).then_some(creator);
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
let description = description.into().trim().to_string();
self.model.setup.description = (!description.is_empty()).then_some(description);
self
}
pub fn keywords(mut self, keywords: impl Into<String>) -> Self {
let keywords = keywords.into().trim().to_string();
self.model.setup.keywords = (!keywords.is_empty()).then_some(keywords);
self
}
pub fn category(mut self, category: impl Into<String>) -> Self {
let category = category.into().trim().to_string();
self.model.setup.category = (!category.is_empty()).then_some(category);
self
}
pub fn content_status(mut self, content_status: impl Into<String>) -> Self {
let content_status = content_status.into().trim().to_string();
self.model.setup.content_status = (!content_status.is_empty()).then_some(content_status);
self
}
pub fn last_modified_by(mut self, last_modified_by: impl Into<String>) -> Self {
let last_modified_by = last_modified_by.into().trim().to_string();
self.model.setup.last_modified_by =
(!last_modified_by.is_empty()).then_some(last_modified_by);
self
}
pub fn created(mut self, created: impl Into<String>) -> Self {
let created = created.into().trim().to_string();
self.model.setup.created = (!created.is_empty()).then_some(created);
self
}
pub fn modified(mut self, modified: impl Into<String>) -> Self {
let modified = modified.into().trim().to_string();
self.model.setup.modified = (!modified.is_empty()).then_some(modified);
self
}
pub fn last_printed(mut self, last_printed: impl Into<String>) -> Self {
let last_printed = last_printed.into().trim().to_string();
self.model.setup.last_printed = (!last_printed.is_empty()).then_some(last_printed);
self
}
pub fn revision(mut self, revision: impl Into<String>) -> Self {
let revision = revision.into().trim().to_string();
self.model.setup.revision = (!revision.is_empty()).then_some(revision);
self
}
pub fn version(mut self, version: impl Into<String>) -> Self {
let version = version.into().trim().to_string();
self.model.setup.version = (!version.is_empty()).then_some(version);
self
}
pub fn custom_property(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
let name = name.into().trim().to_string();
if !name.is_empty() {
self.model.custom_properties.insert(name, value.into());
}
self
}
pub fn custom_xml_item(
mut self,
store_item_id: impl Into<String>,
xml: impl Into<String>,
) -> Self {
let store_item_id = store_item_id.into().trim().to_string();
if !store_item_id.is_empty() {
self.model.custom_xml_items.push(CustomXmlItem {
store_item_id,
xml: xml.into(),
});
}
self
}
pub fn document_id(mut self, id: impl Into<String>) -> Self {
let id = id.into().trim().to_string();
self.model.setup.document_id = (!id.is_empty()).then_some(id);
self
}
pub fn web_extension_task_pane(
mut self,
extension_id: impl Into<String>,
reference_id: impl Into<String>,
version: impl Into<String>,
store: impl Into<String>,
store_type: impl Into<String>,
) -> Self {
let extension_id = extension_id.into().trim().to_string();
let reference_id = reference_id.into().trim().to_string();
let version = version.into().trim().to_string();
let store = store.into().trim().to_string();
let store_type = store_type.into().trim().to_string();
if extension_id.is_empty()
|| reference_id.is_empty()
|| version.is_empty()
|| store.is_empty()
|| store_type.is_empty()
{
return self;
}
let mut properties = std::collections::BTreeMap::new();
properties.insert(
"Office.AutoShowTaskpaneWithDocument".to_string(),
"true".to_string(),
);
self.model
.setup
.web_extension_task_panes
.push(WebExtensionTaskPane {
extension_id,
reference_id,
version,
store,
store_type,
properties,
dock_state: "right".to_string(),
visible: true,
width: 350,
row: 0,
locked: false,
});
self
}
pub fn page_setup(mut self, page: PageSetup) -> Self {
self.model.setup.page = page;
self
}
pub fn page_size_pt(mut self, width_pt: f32, height_pt: f32) -> Self {
self.model.setup.page.width_pt = width_pt;
self.model.setup.page.height_pt = height_pt;
self
}
pub fn landscape(mut self) -> Self {
self.model.setup.page.landscape = true;
self
}
pub fn margins_pt(mut self, margin_pt: f32) -> Self {
self.model.setup.page.margin_pt = margin_pt;
self.model.setup.page.margin_left_pt = None;
self.model.setup.page.margin_right_pt = None;
self.model.setup.page.margin_top_pt = None;
self.model.setup.page.margin_bottom_pt = None;
self
}
pub fn margins_each_pt(
mut self,
top_pt: f32,
right_pt: f32,
bottom_pt: f32,
left_pt: f32,
) -> Self {
self.model.setup.page.margin_top_pt = Some(top_pt);
self.model.setup.page.margin_right_pt = Some(right_pt);
self.model.setup.page.margin_bottom_pt = Some(bottom_pt);
self.model.setup.page.margin_left_pt = Some(left_pt);
self
}
pub fn page_numbers(mut self) -> Self {
self.model.setup.page_numbers = true;
self
}
pub fn no_page_numbers(mut self) -> Self {
self.model.setup.page_numbers = false;
self
}
pub fn page_number_start(mut self, start: u32) -> Self {
self.model.setup.page_number_start = Some(start.max(1));
self
}
pub fn page_number_format(mut self, format: PageNumberFormat) -> Self {
self.model.setup.page_number_format = Some(format);
self
}
pub fn columns(mut self, columns: u16) -> Self {
self.model.setup.columns = Some(columns.max(1));
self
}
pub fn text_direction(mut self, direction: TextDirection) -> Self {
self.model.setup.text_direction = Some(direction);
self
}
pub fn doc_grid_lines(mut self, line_pitch: u32) -> Self {
self.model.setup.doc_grid = Some(DocGrid {
grid_type: DocGridType::Lines,
line_pitch: Some(line_pitch),
character_space: None,
});
self
}
pub fn doc_grid_lines_and_chars(mut self, line_pitch: u32, character_space: u32) -> Self {
self.model.setup.doc_grid = Some(DocGrid {
grid_type: DocGridType::LinesAndChars,
line_pitch: Some(line_pitch),
character_space: Some(character_space),
});
self
}
pub fn doc_grid_snap_to_chars(mut self, character_space: u32) -> Self {
self.model.setup.doc_grid = Some(DocGrid {
grid_type: DocGridType::SnapToChars,
line_pitch: None,
character_space: Some(character_space),
});
self
}
pub fn title_page(mut self) -> Self {
self.model.setup.title_page = true;
self
}
pub fn paragraph_style<S>(mut self, style: S) -> Self
where
S: Into<ParagraphStyle>,
{
self.model.setup.styles.push(style.into());
self
}
pub fn paragraph(mut self, text: impl Into<String>) -> Self {
self.model.blocks.push(plain_paragraph(text.into()));
self
}
pub fn paragraph_runs<I>(mut self, runs: I) -> Self
where
I: IntoIterator<Item = Run>,
{
self.model
.blocks
.push(paragraph(runs, ParaProps::default()));
self
}
pub fn rich_paragraph<P>(mut self, paragraph: P) -> Self
where
P: Into<Paragraph>,
{
self.model.blocks.push(Block::Paragraph(paragraph.into()));
self
}
pub fn heading(mut self, level: u8, text: impl Into<String>) -> Self {
self.model.blocks.push(heading(level, text.into()));
self
}
pub fn heading_runs<I>(mut self, level: u8, runs: I) -> Self
where
I: IntoIterator<Item = Run>,
{
self.model.blocks.push(paragraph(
runs,
ParaProps {
heading_level: Some(level.clamp(1, 6)),
..ParaProps::default()
},
));
self
}
pub fn numbered_list<I, S>(self, items: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.list_level(items, true, 0)
}
pub fn numbered_list_level<I, S>(self, level: u8, items: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.list_level(items, true, level)
}
pub fn bullet_list<I, S>(self, items: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.list_level(items, false, 0)
}
pub fn bullet_list_level<I, S>(self, level: u8, items: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.list_level(items, false, level)
}
pub fn list<I, S>(self, items: I, ordered: bool) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.list_level(items, ordered, 0)
}
pub fn list_level<I, S>(mut self, items: I, ordered: bool, level: u8) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let level = level.min(8);
self.model.blocks.extend(
items
.into_iter()
.map(|text| list_paragraph(text.into(), ordered, level)),
);
self
}
pub fn hyperlink(mut self, text: impl Into<String>, url: impl Into<String>) -> Self {
self.model
.blocks
.push(hyperlink_paragraph(text.into(), url.into()));
self
}
pub fn field(mut self, instruction: impl Into<String>, result: impl Into<String>) -> Self {
let instruction = normalize_field_instruction(&instruction.into());
let field = if instruction.is_empty() {
FieldRole::None
} else {
FieldRole::Simple { instruction }
};
self.model.blocks.push(Block::Paragraph(Paragraph {
props: ParaProps::default(),
runs: vec![Run {
text: result.into(),
field,
..Run::default()
}],
}));
self
}
pub fn toc_heading_range(mut self, start: u8, end: u8) -> Self {
let start = start.clamp(1, 9);
let end = end.clamp(1, 9);
let (start, end) = if start <= end {
(start, end)
} else {
(end, start)
};
self.model.blocks.push(Block::Paragraph(Paragraph {
props: ParaProps::default(),
runs: vec![Run {
text: "Contents".to_string(),
field: FieldRole::Simple {
instruction: format!(r#"TOC \o "{start}-{end}""#),
},
field_dirty: true,
..Run::default()
}],
}));
self
}
pub fn image(mut self, bytes: Vec<u8>, mime: impl Into<String>) -> Self {
self.model.blocks.push(Block::Image(Image {
bytes: Some(bytes),
mime: Some(mime.into()),
..Image::default()
}));
self
}
pub fn rich_image<I>(mut self, image: I) -> Self
where
I: Into<Image>,
{
self.model.blocks.push(Block::Image(image.into()));
self
}
pub fn chart<C>(mut self, chart: C) -> Self
where
C: Into<Chart>,
{
self.model.blocks.push(Block::Chart(chart.into()));
self
}
pub fn page_break(mut self) -> Self {
self.model.blocks.push(Block::PageBreak);
self
}
pub fn section_break(self) -> Self {
self.section_break_kind(SectionBreakKind::NextPage)
}
pub fn section_break_even_page(self) -> Self {
self.section_break_kind(SectionBreakKind::EvenPage)
}
pub fn section_break_odd_page(self) -> Self {
self.section_break_kind(SectionBreakKind::OddPage)
}
fn section_break_kind(mut self, kind: SectionBreakKind) -> Self {
let mut setup = SectionSetup::from(&self.model.setup);
setup.section_break = Some(kind);
self.model.blocks.push(Block::SectionBreak(setup));
self
}
pub fn table<R, C, S>(self, rows: R) -> Self
where
R: IntoIterator<Item = C>,
C: IntoIterator<Item = S>,
S: Into<String>,
{
self.table_with_header_rows(rows, 0)
}
pub fn table_with_header<R, C, S>(self, rows: R) -> Self
where
R: IntoIterator<Item = C>,
C: IntoIterator<Item = S>,
S: Into<String>,
{
self.table_with_header_rows(rows, 1)
}
pub fn table_with_header_rows<R, C, S>(mut self, rows: R, header_rows: usize) -> Self
where
R: IntoIterator<Item = C>,
C: IntoIterator<Item = S>,
S: Into<String>,
{
let rows = rows
.into_iter()
.enumerate()
.map(|(row_index, row)| Row {
cells: row
.into_iter()
.map(|text| plain_cell(text.into(), row_index < header_rows))
.collect(),
})
.collect();
self.model.blocks.push(Block::Table(Table {
rows,
header_rows,
..Table::default()
}));
self
}
pub fn rich_table<T>(mut self, table: T) -> Self
where
T: Into<Table>,
{
self.model.blocks.push(Block::Table(table.into()));
self
}
pub fn header(mut self, text: impl Into<String>) -> Self {
self.model.setup.header.push(plain_paragraph(text.into()));
self
}
pub fn first_header(mut self, text: impl Into<String>) -> Self {
self.model.setup.title_page = true;
self.model
.setup
.first_header
.push(plain_paragraph(text.into()));
self
}
pub fn even_header(mut self, text: impl Into<String>) -> Self {
self.model
.setup
.even_header
.push(plain_paragraph(text.into()));
self
}
pub fn first_header_runs<I>(mut self, runs: I) -> Self
where
I: IntoIterator<Item = Run>,
{
self.model.setup.title_page = true;
self.model
.setup
.first_header
.push(paragraph(runs, ParaProps::default()));
self
}
pub fn even_header_runs<I>(mut self, runs: I) -> Self
where
I: IntoIterator<Item = Run>,
{
self.model
.setup
.even_header
.push(paragraph(runs, ParaProps::default()));
self
}
pub fn header_runs<I>(mut self, runs: I) -> Self
where
I: IntoIterator<Item = Run>,
{
self.model
.setup
.header
.push(paragraph(runs, ParaProps::default()));
self
}
pub fn push_header_block(mut self, block: Block) -> Self {
self.model.setup.header.push(block);
self
}
pub fn push_first_header_block(mut self, block: Block) -> Self {
self.model.setup.title_page = true;
self.model.setup.first_header.push(block);
self
}
pub fn push_even_header_block(mut self, block: Block) -> Self {
self.model.setup.even_header.push(block);
self
}
pub fn clear_header(mut self) -> Self {
self.model.setup.header.clear();
self.model.setup.first_header.clear();
self.model.setup.even_header.clear();
self
}
pub fn footer(mut self, text: impl Into<String>) -> Self {
self.model.setup.footer.push(plain_paragraph(text.into()));
self
}
pub fn first_footer(mut self, text: impl Into<String>) -> Self {
self.model.setup.title_page = true;
self.model
.setup
.first_footer
.push(plain_paragraph(text.into()));
self
}
pub fn even_footer(mut self, text: impl Into<String>) -> Self {
self.model
.setup
.even_footer
.push(plain_paragraph(text.into()));
self
}
pub fn first_footer_runs<I>(mut self, runs: I) -> Self
where
I: IntoIterator<Item = Run>,
{
self.model.setup.title_page = true;
self.model
.setup
.first_footer
.push(paragraph(runs, ParaProps::default()));
self
}
pub fn even_footer_runs<I>(mut self, runs: I) -> Self
where
I: IntoIterator<Item = Run>,
{
self.model
.setup
.even_footer
.push(paragraph(runs, ParaProps::default()));
self
}
pub fn footer_runs<I>(mut self, runs: I) -> Self
where
I: IntoIterator<Item = Run>,
{
self.model
.setup
.footer
.push(paragraph(runs, ParaProps::default()));
self
}
pub fn push_footer_block(mut self, block: Block) -> Self {
self.model.setup.footer.push(block);
self
}
pub fn push_first_footer_block(mut self, block: Block) -> Self {
self.model.setup.title_page = true;
self.model.setup.first_footer.push(block);
self
}
pub fn push_even_footer_block(mut self, block: Block) -> Self {
self.model.setup.even_footer.push(block);
self
}
pub fn clear_footer(mut self) -> Self {
self.model.setup.footer.clear();
self.model.setup.first_footer.clear();
self.model.setup.even_footer.clear();
self
}
pub fn clear_header_footer(self) -> Self {
self.clear_header().clear_footer()
}
pub fn push_block(mut self, block: Block) -> Self {
self.model.blocks.push(block);
self
}
pub fn build(self) -> DocModel {
self.model
}
}
impl From<DocBuilder> for DocModel {
fn from(builder: DocBuilder) -> Self {
builder.build()
}
}
fn plain_paragraph(text: String) -> Block {
paragraph(
[plain_run(text, CharProps::default())],
ParaProps::default(),
)
}
fn heading(level: u8, text: String) -> Block {
paragraph(
[plain_run(text, CharProps::default())],
ParaProps {
heading_level: Some(level.clamp(1, 6)),
..ParaProps::default()
},
)
}
fn paragraph<I>(runs: I, props: ParaProps) -> Block
where
I: IntoIterator<Item = Run>,
{
Block::Paragraph(Paragraph {
props,
runs: runs.into_iter().collect(),
})
}
fn list_paragraph(text: String, ordered: bool, level: u8) -> Block {
Block::Paragraph(Paragraph {
props: ParaProps {
list: Some(ListInfo {
level,
ordered,
label: String::new(),
}),
..ParaProps::default()
},
runs: vec![plain_run(text, CharProps::default())],
})
}
fn hyperlink_paragraph(text: String, url: String) -> Block {
Block::Paragraph(Paragraph {
props: ParaProps::default(),
runs: vec![Run {
text,
field: hyperlink_field(url),
..Run::default()
}],
})
}
fn hyperlink_field(url: String) -> FieldRole {
let url = url.trim().to_string();
if url.is_empty() {
FieldRole::None
} else {
FieldRole::Hyperlink { url }
}
}
fn plain_cell(text: String, is_header: bool) -> Cell {
Cell {
blocks: vec![plain_paragraph(text)],
is_header,
..Cell::default()
}
}
fn plain_run(text: String, props: CharProps) -> Run {
Run {
text,
props,
..Run::default()
}
}