Lines
35.05 %
Functions
7.64 %
Branches
100 %
//! CSS properties for multi-column layout.
//!
//! Covers `column-count`, `column-width`, `column-span`, `column-fill`,
//! `column-rule-width`, `column-rule-style`, and `column-rule-color`.
//! Types are consumed via the `CssProperty` enum in the CSS property system.
use alloc::string::{String, ToString};
use core::num::ParseIntError;
use crate::props::{
basic::{
color::{parse_css_color, ColorU, CssColorParseError, CssColorParseErrorOwned},
pixel::{
parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue,
},
formatter::PrintAsCssValue,
style::border::{
parse_border_style, BorderStyle, CssBorderStyleParseError, CssBorderStyleParseErrorOwned,
};
// --- column-count ---
/// CSS `column-count` property: specifies the number of columns in a multi-column layout.
///
/// Values: `auto` or a positive integer.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
#[derive(Default)]
pub enum ColumnCount {
#[default]
Auto,
Integer(u32),
}
impl PrintAsCssValue for ColumnCount {
fn print_as_css_value(&self) -> String {
match self {
Self::Auto => "auto".to_string(),
Self::Integer(i) => i.to_string(),
// --- column-width ---
/// CSS `column-width` property: specifies the optimal width of columns.
/// Values: `auto` or a length value (e.g. `200px`, `15em`).
pub enum ColumnWidth {
Length(PixelValue),
impl PrintAsCssValue for ColumnWidth {
Self::Length(px) => px.print_as_css_value(),
// --- column-span ---
/// CSS `column-span` property: whether an element spans across all columns.
/// Values: `none` (default) or `all`.
#[repr(C)]
pub enum ColumnSpan {
None,
All,
impl PrintAsCssValue for ColumnSpan {
String::from(match self {
Self::None => "none",
Self::All => "all",
})
// --- column-fill ---
/// CSS `column-fill` property: how content is distributed across columns.
/// Values: `balance` (default) or `auto`.
pub enum ColumnFill {
Balance,
impl PrintAsCssValue for ColumnFill {
Self::Auto => "auto",
Self::Balance => "balance",
// --- column-rule ---
/// CSS `column-rule-width` property: the width of the rule between columns.
/// Defaults to `medium` (3px).
pub struct ColumnRuleWidth {
pub inner: PixelValue,
impl Default for ColumnRuleWidth {
fn default() -> Self {
Self {
inner: PixelValue::const_px(3),
impl PrintAsCssValue for ColumnRuleWidth {
self.inner.print_as_css_value()
/// CSS `column-rule-style` property: the style of the rule between columns.
/// Uses `BorderStyle` values (e.g. `none`, `solid`, `dotted`).
pub struct ColumnRuleStyle {
pub inner: BorderStyle,
impl Default for ColumnRuleStyle {
inner: BorderStyle::None,
impl PrintAsCssValue for ColumnRuleStyle {
/// CSS `column-rule-color` property: the color of the rule between columns.
/// Per the CSS spec this should default to `currentcolor`, but currently
/// defaults to black as `currentcolor` requires a resolved-value pass at
/// layout time.
pub struct ColumnRuleColor {
pub inner: ColorU,
impl Default for ColumnRuleColor {
// NOTE: should be `currentcolor` per CSS spec, see doc comment on type
inner: ColorU::BLACK,
impl PrintAsCssValue for ColumnRuleColor {
self.inner.to_hash()
// Formatting to Rust code
impl crate::format_rust_code::FormatAsRustCode for ColumnCount {
fn format_as_rust_code(&self, _tabs: usize) -> String {
ColumnCount::Auto => String::from("ColumnCount::Auto"),
ColumnCount::Integer(i) => format!("ColumnCount::Integer({})", i),
impl crate::format_rust_code::FormatAsRustCode for ColumnWidth {
ColumnWidth::Auto => String::from("ColumnWidth::Auto"),
ColumnWidth::Length(px) => format!(
"ColumnWidth::Length({})",
crate::format_rust_code::format_pixel_value(px)
),
impl crate::format_rust_code::FormatAsRustCode for ColumnSpan {
ColumnSpan::None => String::from("ColumnSpan::None"),
ColumnSpan::All => String::from("ColumnSpan::All"),
impl crate::format_rust_code::FormatAsRustCode for ColumnFill {
ColumnFill::Auto => String::from("ColumnFill::Auto"),
ColumnFill::Balance => String::from("ColumnFill::Balance"),
impl crate::format_rust_code::FormatAsRustCode for ColumnRuleWidth {
format!(
"ColumnRuleWidth {{ inner: {} }}",
crate::format_rust_code::format_pixel_value(&self.inner)
)
impl crate::format_rust_code::FormatAsRustCode for ColumnRuleStyle {
fn format_as_rust_code(&self, tabs: usize) -> String {
"ColumnRuleStyle {{ inner: {} }}",
self.inner.format_as_rust_code(tabs)
impl crate::format_rust_code::FormatAsRustCode for ColumnRuleColor {
"ColumnRuleColor {{ inner: {} }}",
crate::format_rust_code::format_color_value(&self.inner)
// --- PARSERS ---
#[cfg(feature = "parser")]
pub mod parser {
use super::*;
use crate::corety::AzString;
// -- ColumnCount parser
#[derive(Clone, PartialEq)]
pub enum ColumnCountParseError<'a> {
InvalidValue(&'a str),
ParseInt(ParseIntError),
impl_debug_as_display!(ColumnCountParseError<'a>);
impl_display! { ColumnCountParseError<'a>, {
InvalidValue(v) => format!("Invalid column-count value: \"{}\"", v),
ParseInt(e) => format!("Invalid integer for column-count: {}", e),
}}
#[derive(Debug, Clone, PartialEq)]
pub enum ColumnCountParseErrorOwned {
InvalidValue(AzString),
ParseInt(AzString),
impl<'a> ColumnCountParseError<'a> {
pub fn to_contained(&self) -> ColumnCountParseErrorOwned {
Self::InvalidValue(s) => ColumnCountParseErrorOwned::InvalidValue(s.to_string().into()),
Self::ParseInt(e) => ColumnCountParseErrorOwned::ParseInt(e.to_string().into()),
impl ColumnCountParseErrorOwned {
pub fn to_shared<'a>(&'a self) -> ColumnCountParseError<'a> {
Self::InvalidValue(s) => ColumnCountParseError::InvalidValue(s),
// ParseIntError cannot be reconstructed from its Display string,
// so we fall back to a generic message. The original error text
// is preserved in the owned `AzString` but not round-trippable.
Self::ParseInt(_) => ColumnCountParseError::InvalidValue("invalid integer"),
pub fn parse_column_count<'a>(
input: &'a str,
) -> Result<ColumnCount, ColumnCountParseError<'a>> {
let trimmed = input.trim();
if trimmed == "auto" {
return Ok(ColumnCount::Auto);
let val: u32 = trimmed
.parse()
.map_err(ColumnCountParseError::ParseInt)?;
Ok(ColumnCount::Integer(val))
// -- ColumnWidth parser
pub enum ColumnWidthParseError<'a> {
PixelValue(CssPixelValueParseError<'a>),
impl_debug_as_display!(ColumnWidthParseError<'a>);
impl_display! { ColumnWidthParseError<'a>, {
InvalidValue(v) => format!("Invalid column-width value: \"{}\"", v),
PixelValue(e) => format!("{}", e),
impl_from! { CssPixelValueParseError<'a>, ColumnWidthParseError::PixelValue }
pub enum ColumnWidthParseErrorOwned {
PixelValue(CssPixelValueParseErrorOwned),
impl<'a> ColumnWidthParseError<'a> {
pub fn to_contained(&self) -> ColumnWidthParseErrorOwned {
Self::InvalidValue(s) => ColumnWidthParseErrorOwned::InvalidValue(s.to_string().into()),
Self::PixelValue(e) => ColumnWidthParseErrorOwned::PixelValue(e.to_contained()),
impl ColumnWidthParseErrorOwned {
pub fn to_shared<'a>(&'a self) -> ColumnWidthParseError<'a> {
Self::InvalidValue(s) => ColumnWidthParseError::InvalidValue(s),
Self::PixelValue(e) => ColumnWidthParseError::PixelValue(e.to_shared()),
pub fn parse_column_width<'a>(
) -> Result<ColumnWidth, ColumnWidthParseError<'a>> {
return Ok(ColumnWidth::Auto);
Ok(ColumnWidth::Length(parse_pixel_value(trimmed)?))
// -- Other column parsers...
macro_rules! define_simple_column_parser {
(
$fn_name:ident,
$struct_name:ident,
$error_name:ident,
$error_owned_name:ident,
$prop_name:expr,
$($val:expr => $variant:path),+
) => {
pub enum $error_name<'a> {
impl_debug_as_display!($error_name<'a>);
impl_display! { $error_name<'a>, {
InvalidValue(v) => format!("Invalid {} value: \"{}\"", $prop_name, v),
pub enum $error_owned_name {
impl<'a> $error_name<'a> {
pub fn to_contained(&self) -> $error_owned_name {
Self::InvalidValue(s) => $error_owned_name::InvalidValue(s.to_string().into()),
impl $error_owned_name {
pub fn to_shared<'a>(&'a self) -> $error_name<'a> {
Self::InvalidValue(s) => $error_name::InvalidValue(s.as_str()),
pub fn $fn_name<'a>(input: &'a str) -> Result<$struct_name, $error_name<'a>> {
match input.trim() {
$( $val => Ok($variant), )+
_ => Err($error_name::InvalidValue(input)),
define_simple_column_parser!(
parse_column_span,
ColumnSpan,
ColumnSpanParseError,
ColumnSpanParseErrorOwned,
"column-span",
"none" => ColumnSpan::None,
"all" => ColumnSpan::All
);
parse_column_fill,
ColumnFill,
ColumnFillParseError,
ColumnFillParseErrorOwned,
"column-fill",
"auto" => ColumnFill::Auto,
"balance" => ColumnFill::Balance
// Parsers for column-rule-*
pub enum ColumnRuleWidthParseError<'a> {
Pixel(CssPixelValueParseError<'a>),
impl_debug_as_display!(ColumnRuleWidthParseError<'a>);
impl_display! { ColumnRuleWidthParseError<'a>, { Pixel(e) => format!("{}", e) }}
impl_from! { CssPixelValueParseError<'a>, ColumnRuleWidthParseError::Pixel }
pub enum ColumnRuleWidthParseErrorOwned {
Pixel(CssPixelValueParseErrorOwned),
impl<'a> ColumnRuleWidthParseError<'a> {
pub fn to_contained(&self) -> ColumnRuleWidthParseErrorOwned {
ColumnRuleWidthParseError::Pixel(e) => {
ColumnRuleWidthParseErrorOwned::Pixel(e.to_contained())
impl ColumnRuleWidthParseErrorOwned {
pub fn to_shared<'a>(&'a self) -> ColumnRuleWidthParseError<'a> {
ColumnRuleWidthParseErrorOwned::Pixel(e) => {
ColumnRuleWidthParseError::Pixel(e.to_shared())
pub fn parse_column_rule_width<'a>(
) -> Result<ColumnRuleWidth, ColumnRuleWidthParseError<'a>> {
Ok(ColumnRuleWidth {
inner: parse_pixel_value(input)?,
pub enum ColumnRuleStyleParseError<'a> {
Style(CssBorderStyleParseError<'a>),
impl_debug_as_display!(ColumnRuleStyleParseError<'a>);
impl_display! { ColumnRuleStyleParseError<'a>, { Style(e) => format!("{}", e) }}
impl_from! { CssBorderStyleParseError<'a>, ColumnRuleStyleParseError::Style }
pub enum ColumnRuleStyleParseErrorOwned {
Style(CssBorderStyleParseErrorOwned),
impl<'a> ColumnRuleStyleParseError<'a> {
pub fn to_contained(&self) -> ColumnRuleStyleParseErrorOwned {
ColumnRuleStyleParseError::Style(e) => {
ColumnRuleStyleParseErrorOwned::Style(e.to_contained())
impl ColumnRuleStyleParseErrorOwned {
pub fn to_shared<'a>(&'a self) -> ColumnRuleStyleParseError<'a> {
ColumnRuleStyleParseErrorOwned::Style(e) => {
ColumnRuleStyleParseError::Style(e.to_shared())
pub fn parse_column_rule_style<'a>(
) -> Result<ColumnRuleStyle, ColumnRuleStyleParseError<'a>> {
Ok(ColumnRuleStyle {
inner: parse_border_style(input)?,
pub enum ColumnRuleColorParseError<'a> {
Color(CssColorParseError<'a>),
impl_debug_as_display!(ColumnRuleColorParseError<'a>);
impl_display! { ColumnRuleColorParseError<'a>, { Color(e) => format!("{}", e) }}
impl_from! { CssColorParseError<'a>, ColumnRuleColorParseError::Color }
pub enum ColumnRuleColorParseErrorOwned {
Color(CssColorParseErrorOwned),
impl<'a> ColumnRuleColorParseError<'a> {
pub fn to_contained(&self) -> ColumnRuleColorParseErrorOwned {
ColumnRuleColorParseError::Color(e) => {
ColumnRuleColorParseErrorOwned::Color(e.to_contained())
impl ColumnRuleColorParseErrorOwned {
pub fn to_shared<'a>(&'a self) -> ColumnRuleColorParseError<'a> {
ColumnRuleColorParseErrorOwned::Color(e) => {
ColumnRuleColorParseError::Color(e.to_shared())
pub fn parse_column_rule_color<'a>(
) -> Result<ColumnRuleColor, ColumnRuleColorParseError<'a>> {
Ok(ColumnRuleColor {
inner: parse_css_color(input)?,
pub use parser::*;
#[cfg(all(test, feature = "parser"))]
mod tests {
#[test]
fn test_parse_column_count() {
assert_eq!(parse_column_count("auto").unwrap(), ColumnCount::Auto);
assert_eq!(parse_column_count("3").unwrap(), ColumnCount::Integer(3));
assert!(parse_column_count("none").is_err());
assert!(parse_column_count("2.5").is_err());
fn test_parse_column_width() {
assert_eq!(parse_column_width("auto").unwrap(), ColumnWidth::Auto);
assert_eq!(
parse_column_width("200px").unwrap(),
ColumnWidth::Length(PixelValue::px(200.0))
parse_column_width("15em").unwrap(),
ColumnWidth::Length(PixelValue::em(15.0))
assert!(parse_column_width("50%").is_ok()); // Percentage is valid for column-width
fn test_parse_column_span() {
assert_eq!(parse_column_span("none").unwrap(), ColumnSpan::None);
assert_eq!(parse_column_span("all").unwrap(), ColumnSpan::All);
assert!(parse_column_span("2").is_err());
fn test_parse_column_fill() {
assert_eq!(parse_column_fill("auto").unwrap(), ColumnFill::Auto);
assert_eq!(parse_column_fill("balance").unwrap(), ColumnFill::Balance);
assert!(parse_column_fill("none").is_err());
fn test_parse_column_rule() {
parse_column_rule_width("5px").unwrap().inner,
PixelValue::px(5.0)
parse_column_rule_style("dotted").unwrap().inner,
BorderStyle::Dotted
assert_eq!(parse_column_rule_color("blue").unwrap().inner, ColorU::BLUE);