Lines
64.86 %
Functions
18.18 %
Branches
100 %
//! CSS properties for table layout and styling.
//!
//! This module contains properties specific to CSS table formatting:
//! - `table-layout`: Controls the algorithm used to layout table cells, rows, and columns
//! - `border-collapse`: Specifies whether cell borders are collapsed into a single border or
//! separated
//! - `border-spacing`: Sets the distance between borders of adjacent cells (separate borders only)
//! - `caption-side`: Specifies the placement of a table caption
//! - `empty-cells`: Specifies whether or not to display borders on empty cells in a table
use alloc::string::{String, ToString};
use crate::{
format_rust_code::FormatAsRustCode,
props::{
basic::pixel::{CssPixelValueParseError, PixelValue},
formatter::PrintAsCssValue,
},
};
// table-layout
/// Controls the algorithm used to lay out table cells, rows, and columns.
///
/// The `table-layout` property determines whether the browser should use:
/// - **auto**: Column widths are determined by the content (slower but flexible)
/// - **fixed**: Column widths are determined by the first row (faster and predictable)
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
#[derive(Default)]
pub enum LayoutTableLayout {
/// Use automatic table layout algorithm (content-based, default).
/// Column width is set by the widest unbreakable content in the cells.
#[default]
Auto,
/// Use fixed table layout algorithm (first-row-based).
/// Column width is set by the width property of the column or first-row cell.
/// Renders faster than auto.
Fixed,
}
impl PrintAsCssValue for LayoutTableLayout {
fn print_as_css_value(&self) -> String {
match self {
LayoutTableLayout::Auto => "auto".to_string(),
LayoutTableLayout::Fixed => "fixed".to_string(),
impl FormatAsRustCode for LayoutTableLayout {
fn format_as_rust_code(&self, _tabs: usize) -> String {
LayoutTableLayout::Auto => "LayoutTableLayout::Auto".to_string(),
LayoutTableLayout::Fixed => "LayoutTableLayout::Fixed".to_string(),
// border-collapse
/// Specifies whether cell borders are collapsed into a single border or separated.
/// The `border-collapse` property determines the border rendering model:
/// - **separate**: Each cell has its own border (default, uses border-spacing)
/// - **collapse**: Adjacent cells share borders (ignores border-spacing)
pub enum StyleBorderCollapse {
/// Borders are separated (default). Each cell has its own border.
/// The `border-spacing` property defines the distance between borders.
Separate,
/// Borders are collapsed. Adjacent cells share a single border.
/// Border conflict resolution rules apply when borders differ.
Collapse,
impl PrintAsCssValue for StyleBorderCollapse {
StyleBorderCollapse::Separate => "separate".to_string(),
StyleBorderCollapse::Collapse => "collapse".to_string(),
impl FormatAsRustCode for StyleBorderCollapse {
StyleBorderCollapse::Separate => "StyleBorderCollapse::Separate".to_string(),
StyleBorderCollapse::Collapse => "StyleBorderCollapse::Collapse".to_string(),
// border-spacing
/// Sets the distance between the borders of adjacent cells.
/// The `border-spacing` property is only applicable when `border-collapse` is set to `separate`.
/// It can have one or two values:
/// - One value: Sets both horizontal and vertical spacing
/// - Two values: First is horizontal, second is vertical
/// This struct represents a single spacing value (either horizontal or vertical).
pub struct LayoutBorderSpacing {
/// Horizontal spacing between cell borders
pub horizontal: PixelValue,
/// Vertical spacing between cell borders
pub vertical: PixelValue,
impl Default for LayoutBorderSpacing {
fn default() -> Self {
// Default border-spacing is 0 (no spacing)
Self {
horizontal: PixelValue::const_px(0),
vertical: PixelValue::const_px(0),
impl LayoutBorderSpacing {
/// Creates a new border spacing with the same value for horizontal and vertical
pub const fn new(spacing: PixelValue) -> Self {
horizontal: spacing,
vertical: spacing,
/// Creates a new border spacing with different horizontal and vertical values
pub const fn new_separate(horizontal: PixelValue, vertical: PixelValue) -> Self {
horizontal,
vertical,
impl PrintAsCssValue for LayoutBorderSpacing {
if self.horizontal == self.vertical {
// Single value: same for both dimensions
self.horizontal.to_string()
} else {
// Two values: horizontal vertical
format!("{} {}", self.horizontal, self.vertical)
impl FormatAsRustCode for LayoutBorderSpacing {
use crate::format_rust_code::format_pixel_value;
format!(
"LayoutBorderSpacing {{ horizontal: {}, vertical: {} }}",
format_pixel_value(&self.horizontal),
format_pixel_value(&self.vertical)
)
// caption-side
/// Specifies the placement of a table caption.
/// The `caption-side` property positions the caption either above or below the table.
pub enum StyleCaptionSide {
/// Caption is placed above the table (default)
Top,
/// Caption is placed below the table
Bottom,
impl PrintAsCssValue for StyleCaptionSide {
StyleCaptionSide::Top => "top".to_string(),
StyleCaptionSide::Bottom => "bottom".to_string(),
impl FormatAsRustCode for StyleCaptionSide {
StyleCaptionSide::Top => "StyleCaptionSide::Top".to_string(),
StyleCaptionSide::Bottom => "StyleCaptionSide::Bottom".to_string(),
// empty-cells
/// Specifies whether or not to display borders and background on empty cells.
/// The `empty-cells` property only applies when `border-collapse` is set to `separate`.
/// A cell is considered empty if it contains no visible content.
pub enum StyleEmptyCells {
/// Show borders and background on empty cells (default)
Show,
/// Hide borders and background on empty cells
Hide,
impl PrintAsCssValue for StyleEmptyCells {
StyleEmptyCells::Show => "show".to_string(),
StyleEmptyCells::Hide => "hide".to_string(),
impl FormatAsRustCode for StyleEmptyCells {
StyleEmptyCells::Show => "StyleEmptyCells::Show".to_string(),
StyleEmptyCells::Hide => "StyleEmptyCells::Hide".to_string(),
// Parsing Functions
/// Parse errors for table-layout property
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum LayoutTableLayoutParseError<'a> {
InvalidKeyword(&'a str),
/// Parse a table-layout value from a string
pub(crate) fn parse_table_layout<'a>(
input: &'a str,
) -> Result<LayoutTableLayout, LayoutTableLayoutParseError<'a>> {
match input.trim() {
"auto" => Ok(LayoutTableLayout::Auto),
"fixed" => Ok(LayoutTableLayout::Fixed),
other => Err(LayoutTableLayoutParseError::InvalidKeyword(other)),
/// Parse errors for border-collapse property
pub(crate) enum StyleBorderCollapseParseError<'a> {
/// Parse a border-collapse value from a string
pub(crate) fn parse_border_collapse<'a>(
) -> Result<StyleBorderCollapse, StyleBorderCollapseParseError<'a>> {
"separate" => Ok(StyleBorderCollapse::Separate),
"collapse" => Ok(StyleBorderCollapse::Collapse),
other => Err(StyleBorderCollapseParseError::InvalidKeyword(other)),
/// Parse errors for border-spacing property
pub(crate) enum LayoutBorderSpacingParseError<'a> {
PixelValue(CssPixelValueParseError<'a>),
InvalidFormat,
/// Parse a border-spacing value from a string
/// Accepts: "5px" or "5px 10px"
pub(crate) fn parse_border_spacing<'a>(
) -> Result<LayoutBorderSpacing, LayoutBorderSpacingParseError<'a>> {
use crate::props::basic::parse_pixel_value;
let parts: Vec<&str> = input.split_whitespace().collect();
match parts.len() {
1 => {
// Single value: use for both horizontal and vertical
let value =
parse_pixel_value(parts[0]).map_err(LayoutBorderSpacingParseError::PixelValue)?;
Ok(LayoutBorderSpacing::new(value))
2 => {
let horizontal =
let vertical =
parse_pixel_value(parts[1]).map_err(LayoutBorderSpacingParseError::PixelValue)?;
Ok(LayoutBorderSpacing::new_separate(horizontal, vertical))
_ => Err(LayoutBorderSpacingParseError::InvalidFormat),
/// Parse errors for caption-side property
pub(crate) enum StyleCaptionSideParseError<'a> {
/// Parse a caption-side value from a string
pub(crate) fn parse_caption_side<'a>(
) -> Result<StyleCaptionSide, StyleCaptionSideParseError<'a>> {
"top" => Ok(StyleCaptionSide::Top),
"bottom" => Ok(StyleCaptionSide::Bottom),
other => Err(StyleCaptionSideParseError::InvalidKeyword(other)),
/// Parse errors for empty-cells property
pub(crate) enum StyleEmptyCellsParseError<'a> {
/// Parse an empty-cells value from a string
pub(crate) fn parse_empty_cells<'a>(
) -> Result<StyleEmptyCells, StyleEmptyCellsParseError<'a>> {
"show" => Ok(StyleEmptyCells::Show),
"hide" => Ok(StyleEmptyCells::Hide),
other => Err(StyleEmptyCellsParseError::InvalidKeyword(other)),
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_table_layout() {
assert_eq!(parse_table_layout("auto").unwrap(), LayoutTableLayout::Auto);
assert_eq!(
parse_table_layout("fixed").unwrap(),
LayoutTableLayout::Fixed
);
assert!(parse_table_layout("invalid").is_err());
fn test_parse_border_collapse() {
parse_border_collapse("separate").unwrap(),
StyleBorderCollapse::Separate
parse_border_collapse("collapse").unwrap(),
StyleBorderCollapse::Collapse
assert!(parse_border_collapse("invalid").is_err());
fn test_parse_border_spacing() {
let spacing1 = parse_border_spacing("5px").unwrap();
assert_eq!(spacing1.horizontal, PixelValue::const_px(5));
assert_eq!(spacing1.vertical, PixelValue::const_px(5));
let spacing2 = parse_border_spacing("5px 10px").unwrap();
assert_eq!(spacing2.horizontal, PixelValue::const_px(5));
assert_eq!(spacing2.vertical, PixelValue::const_px(10));
fn test_parse_caption_side() {
assert_eq!(parse_caption_side("top").unwrap(), StyleCaptionSide::Top);
parse_caption_side("bottom").unwrap(),
StyleCaptionSide::Bottom
assert!(parse_caption_side("invalid").is_err());
fn test_parse_empty_cells() {
assert_eq!(parse_empty_cells("show").unwrap(), StyleEmptyCells::Show);
assert_eq!(parse_empty_cells("hide").unwrap(), StyleEmptyCells::Hide);
assert!(parse_empty_cells("invalid").is_err());