diff --git a/crates/formats/src/html.rs b/crates/formats/src/html.rs
index 91d328623..56093f471 100644
--- a/crates/formats/src/html.rs
+++ b/crates/formats/src/html.rs
@@ -13,6 +13,7 @@
use std::sync::Arc;
+use wordcraft_doc::props::VAlign;
use wordcraft_doc::{Align, Document};
use crate::model::{
@@ -1257,6 +1258,26 @@ impl<'r> Builder<'r> {
}
let num = |k: &str| attr(attrs, k).and_then(|v| v.trim().parse::().ok()).map(|v| v.clamp(1, 63));
let shading = attr(attrs, "bgcolor").and_then(parse_color).or(fmt.background);
+ let mut valign = attr(attrs, "valign").and_then(|v| match v.trim().to_ascii_lowercase().as_str() {
+ "top" => Some(VAlign::Top),
+ "middle" | "center" => Some(VAlign::Center),
+ "bottom" => Some(VAlign::Bottom),
+ _ => None,
+ });
+ if let Some(st) = attr(attrs, "style") {
+ for decl in st.split(';') {
+ if let Some((k, v)) = decl.split_once(':') {
+ if k.trim().eq_ignore_ascii_case("vertical-align") {
+ match v.trim().to_ascii_lowercase().as_str() {
+ "top" => valign = Some(VAlign::Top),
+ "middle" | "center" => valign = Some(VAlign::Center),
+ "bottom" => valign = Some(VAlign::Bottom),
+ _ => {}
+ }
+ }
+ }
+ }
+ }
self.cells.push(Cell {
blocks: Vec::new(),
colspan: num("colspan").unwrap_or(1),
@@ -1265,6 +1286,7 @@ impl<'r> Builder<'r> {
header: name == "th",
shading,
borders: None,
+ valign,
});
fmt.background = None;
self.containers.push(Vec::new());
@@ -1617,7 +1639,12 @@ fn table_html(t: &FTable, out: &mut String, depth: usize) {
if c.rowspan > 1 {
attrs.push_str(&format!(" rowspan=\"{}\"", c.rowspan));
}
- let mut css = vec!["padding:4px 6px".to_string(), "vertical-align:top".to_string()];
+ let v_css = match c.valign {
+ Some(VAlign::Center) => "vertical-align:middle",
+ Some(VAlign::Bottom) => "vertical-align:bottom",
+ _ => "vertical-align:top",
+ };
+ let mut css = vec!["padding:4px 6px".to_string(), v_css.to_string()];
if !t.borderless {
css.insert(0, "border:1px solid #999".to_string());
}
@@ -2036,4 +2063,22 @@ mod tests {
};
assert_eq!(p.text(), "World");
}
+
+ #[test]
+ fn table_cell_vertical_alignment() {
+ let f = parse("");
+ let FBlock::Table(t) = &f.blocks[0] else { panic!() };
+ assert_eq!(t.rows[0][0].valign, Some(VAlign::Top));
+ assert_eq!(t.rows[0][1].valign, Some(VAlign::Center));
+ assert_eq!(t.rows[0][2].valign, Some(VAlign::Bottom));
+ let doc = crate::model::to_doc(&f);
+ let wordcraft_doc::Block::Table(dt) = &*doc.body[0] else { panic!() };
+ assert_eq!(dt.rows[0].cells[0].props.valign, Some(VAlign::Top));
+ assert_eq!(dt.rows[0].cells[1].props.valign, Some(VAlign::Center));
+ assert_eq!(dt.rows[0].cells[2].props.valign, Some(VAlign::Bottom));
+ let html = export(&doc);
+ assert!(html.contains("vertical-align:top"));
+ assert!(html.contains("vertical-align:middle"));
+ assert!(html.contains("vertical-align:bottom"));
+ }
}
diff --git a/crates/formats/src/model.rs b/crates/formats/src/model.rs
index c2502f2e4..0a6cfd59c 100644
--- a/crates/formats/src/model.rs
+++ b/crates/formats/src/model.rs
@@ -7,7 +7,7 @@ use std::sync::Arc;
use wordcraft_doc::numbering::{AbstractNum, Level, ListKind, Num, levels_for};
use wordcraft_doc::para::{InlineObject, OBJ, PAGE_BREAK, Run};
use wordcraft_doc::props::{
- Border, BorderStyle, Borders, CellProps, CharProps, NumRef, ParaProps, PropChange, RowProps, TextColor, Underline, VMerge, VertAlign,
+ Border, BorderStyle, Borders, CellProps, CharProps, NumRef, ParaProps, PropChange, RowProps, TextColor, Underline, VAlign, VMerge, VertAlign,
};
use wordcraft_doc::section::NumFormat;
use wordcraft_doc::styles::{Style, StyleKind};
@@ -242,11 +242,12 @@ pub struct Cell {
pub shading: Option,
/// The cell's own borders (RTF `\clbrdr…`), drawn whatever the table's style.
pub borders: Option,
+ pub valign: Option,
}
impl Default for Cell {
fn default() -> Self {
- Cell { blocks: Vec::new(), colspan: 1, rowspan: 1, covered: false, header: false, shading: None, borders: None }
+ Cell { blocks: Vec::new(), colspan: 1, rowspan: 1, covered: false, header: false, shading: None, borders: None, valign: None }
}
}
@@ -693,7 +694,15 @@ impl<'a> Builder<'a> {
VMerge::None
};
row.cells.push(wordcraft_doc::Cell {
- props: CellProps { width: Some(w), span: span as u32, vmerge, shading: c.shading, borders: c.borders, ..Default::default() },
+ props: CellProps {
+ width: Some(w),
+ span: span as u32,
+ vmerge,
+ shading: c.shading,
+ borders: c.borders,
+ valign: c.valign,
+ ..Default::default()
+ },
blocks,
controls: Default::default(),
});
@@ -1048,6 +1057,7 @@ fn flow_table(doc: &Document, t: &Table, depth: usize, tracked: bool) -> FTable
header,
shading: c.props.shading,
borders: c.props.borders,
+ valign: c.props.valign,
});
}
rows.push(cells);
diff --git a/crates/formats/src/odt.rs b/crates/formats/src/odt.rs
index 86ee6acee..477da63c2 100644
--- a/crates/formats/src/odt.rs
+++ b/crates/formats/src/odt.rs
@@ -22,6 +22,7 @@ use std::io::{Cursor, Read, Write};
use quick_xml::XmlVersion;
use quick_xml::events::{BytesStart, Event};
+use wordcraft_doc::props::VAlign;
use wordcraft_doc::section::{Columns, SectionStart};
use wordcraft_doc::{Align, Document, RevisionKind, Rgb, SectionProps};
@@ -103,8 +104,8 @@ struct Writer {
last_list: Option,
/// The page the block being written starts.
master: Option,
- /// Cell styles by shading.
- cell_styles: Vec