use std::collections::HashMap;
use std::fmt::Write;
use std::hash::{Hash, Hasher};
pub trait ToCss {
fn to_css(&self, buf: &mut String);
fn to_css_string(&self) -> String {
let mut buf = String::new();
self.to_css(&mut buf);
buf
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct StyleId(pub u32);
impl StyleId {
pub const DEFAULT: StyleId = StyleId(0);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct FontWeight(pub u16);
impl FontWeight {
pub const NORMAL: FontWeight = FontWeight(400);
pub const BOLD: FontWeight = FontWeight(700);
}
impl ToCss for FontWeight {
fn to_css(&self, buf: &mut String) {
match self.0 {
400 => buf.push_str("normal"),
700 => buf.push_str("bold"),
w => write!(buf, "{}", w).unwrap(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FontStyle {
#[default]
Normal,
Italic,
Oblique,
}
impl ToCss for FontStyle {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
FontStyle::Normal => "normal",
FontStyle::Italic => "italic",
FontStyle::Oblique => "oblique",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FontVariant {
#[default]
Normal,
SmallCaps,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TextTransform {
#[default]
None,
Uppercase,
Lowercase,
Capitalize,
}
impl ToCss for TextTransform {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
TextTransform::None => "none",
TextTransform::Uppercase => "uppercase",
TextTransform::Lowercase => "lowercase",
TextTransform::Capitalize => "capitalize",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Hyphens {
Auto,
#[default]
Manual,
None,
}
impl ToCss for Hyphens {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
Hyphens::Auto => "auto",
Hyphens::Manual => "manual",
Hyphens::None => "none",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum DecorationStyle {
#[default]
None,
Solid,
Dotted,
Dashed,
Double,
}
impl ToCss for DecorationStyle {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
DecorationStyle::None => "none",
DecorationStyle::Solid => "solid",
DecorationStyle::Dotted => "dotted",
DecorationStyle::Dashed => "dashed",
DecorationStyle::Double => "double",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Float {
#[default]
None,
Left,
Right,
}
impl ToCss for Float {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
Float::None => "none",
Float::Left => "left",
Float::Right => "right",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BreakValue {
#[default]
Auto,
Always,
Avoid,
Column,
}
impl ToCss for BreakValue {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
BreakValue::Auto => "auto",
BreakValue::Always => "always",
BreakValue::Avoid => "avoid",
BreakValue::Column => "column",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BorderStyle {
#[default]
None,
Solid,
Dotted,
Dashed,
Double,
Groove,
Ridge,
Inset,
Outset,
}
impl ToCss for BorderStyle {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
BorderStyle::None => "none",
BorderStyle::Solid => "solid",
BorderStyle::Dotted => "dotted",
BorderStyle::Dashed => "dashed",
BorderStyle::Double => "double",
BorderStyle::Groove => "groove",
BorderStyle::Ridge => "ridge",
BorderStyle::Inset => "inset",
BorderStyle::Outset => "outset",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ListStylePosition {
#[default]
Outside,
Inside,
}
impl ToCss for ListStylePosition {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
ListStylePosition::Outside => "outside",
ListStylePosition::Inside => "inside",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Visibility {
#[default]
Visible,
Hidden,
Collapse,
}
impl ToCss for Visibility {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
Visibility::Visible => "visible",
Visibility::Hidden => "hidden",
Visibility::Collapse => "collapse",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BoxSizing {
#[default]
ContentBox,
BorderBox,
}
impl ToCss for BoxSizing {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
BoxSizing::ContentBox => "content-box",
BoxSizing::BorderBox => "border-box",
});
}
}
impl ToCss for FontVariant {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
FontVariant::Normal => "normal",
FontVariant::SmallCaps => "small-caps",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TextAlign {
#[default]
Start,
End,
Left,
Right,
Center,
Justify,
}
impl ToCss for TextAlign {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
TextAlign::Start => "start",
TextAlign::End => "end",
TextAlign::Left => "left",
TextAlign::Right => "right",
TextAlign::Center => "center",
TextAlign::Justify => "justify",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Display {
#[default]
Block,
Inline,
None,
ListItem,
TableCell,
TableRow,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ListStyleType {
#[default]
None,
Disc,
Circle,
Square,
Decimal,
LowerAlpha,
UpperAlpha,
LowerRoman,
UpperRoman,
}
impl ToCss for ListStyleType {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
ListStyleType::None => "none",
ListStyleType::Disc => "disc",
ListStyleType::Circle => "circle",
ListStyleType::Square => "square",
ListStyleType::Decimal => "decimal",
ListStyleType::LowerAlpha => "lower-alpha",
ListStyleType::UpperAlpha => "upper-alpha",
ListStyleType::LowerRoman => "lower-roman",
ListStyleType::UpperRoman => "upper-roman",
});
}
}
impl ToCss for Display {
fn to_css(&self, buf: &mut String) {
buf.push_str(match self {
Display::Block => "block",
Display::Inline => "inline",
Display::None => "none",
Display::ListItem => "list-item",
Display::TableCell => "table-cell",
Display::TableRow => "table-row",
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Color {
pub const BLACK: Color = Color {
r: 0,
g: 0,
b: 0,
a: 255,
};
pub const WHITE: Color = Color {
r: 255,
g: 255,
b: 255,
a: 255,
};
pub const TRANSPARENT: Color = Color {
r: 0,
g: 0,
b: 0,
a: 0,
};
pub fn rgb(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b, a: 255 }
}
pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
}
impl ToCss for Color {
fn to_css(&self, buf: &mut String) {
if self.a == 255 {
write!(buf, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b).unwrap();
} else if self.a == 0 {
buf.push_str("transparent");
} else {
let alpha = self.a as f32 / 255.0;
write!(buf, "rgba({},{},{},{:.2})", self.r, self.g, self.b, alpha).unwrap();
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Length {
#[default]
Auto,
Px(f32),
Em(f32),
Rem(f32),
Percent(f32),
}
impl Eq for Length {}
impl Hash for Length {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Length::Auto => 0u8.hash(state),
Length::Px(v) => {
1u8.hash(state);
v.to_bits().hash(state);
}
Length::Em(v) => {
2u8.hash(state);
v.to_bits().hash(state);
}
Length::Rem(v) => {
3u8.hash(state);
v.to_bits().hash(state);
}
Length::Percent(v) => {
4u8.hash(state);
v.to_bits().hash(state);
}
}
}
}
impl ToCss for Length {
fn to_css(&self, buf: &mut String) {
match self {
Length::Auto => buf.push_str("auto"),
Length::Px(v) => {
if *v == 0.0 {
buf.push('0');
} else {
write!(buf, "{}px", v).unwrap();
}
}
Length::Em(v) => write!(buf, "{}em", v).unwrap(),
Length::Rem(v) => write!(buf, "{}rem", v).unwrap(),
Length::Percent(v) => write!(buf, "{}%", v).unwrap(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct ComputedStyle {
pub font_family: Option<String>,
pub font_size: Length,
pub font_weight: FontWeight,
pub font_style: FontStyle,
pub color: Option<Color>,
pub background_color: Option<Color>,
pub text_align: TextAlign,
pub text_indent: Length,
pub line_height: Length,
pub text_decoration_underline: bool,
pub text_decoration_line_through: bool,
pub display: Display,
pub margin_top: Length,
pub margin_bottom: Length,
pub margin_left: Length,
pub margin_right: Length,
pub padding_top: Length,
pub padding_bottom: Length,
pub padding_left: Length,
pub padding_right: Length,
pub vertical_align_super: bool,
pub vertical_align_sub: bool,
pub list_style_type: ListStyleType,
pub font_variant: FontVariant,
pub letter_spacing: Length,
pub word_spacing: Length,
pub text_transform: TextTransform,
pub hyphens: Hyphens,
pub no_break: bool,
pub underline_style: DecorationStyle,
pub overline: bool,
pub underline_color: Option<Color>,
pub width: Length,
pub height: Length,
pub max_width: Length,
pub min_height: Length,
pub float: Float,
pub break_before: BreakValue,
pub break_after: BreakValue,
pub break_inside: BreakValue,
pub border_style_top: BorderStyle,
pub border_style_right: BorderStyle,
pub border_style_bottom: BorderStyle,
pub border_style_left: BorderStyle,
pub border_width_top: Length,
pub border_width_right: Length,
pub border_width_bottom: Length,
pub border_width_left: Length,
pub border_color_top: Option<Color>,
pub border_color_right: Option<Color>,
pub border_color_bottom: Option<Color>,
pub border_color_left: Option<Color>,
pub border_radius_top_left: Length,
pub border_radius_top_right: Length,
pub border_radius_bottom_left: Length,
pub border_radius_bottom_right: Length,
pub list_style_position: ListStylePosition,
pub language: Option<String>,
pub visibility: Visibility,
pub box_sizing: BoxSizing,
}
impl ComputedStyle {
pub fn is_default(&self) -> bool {
*self == ComputedStyle::default()
}
#[inline]
pub fn is_bold(&self) -> bool {
self.font_weight.0 >= 700
}
#[inline]
pub fn is_italic(&self) -> bool {
matches!(self.font_style, FontStyle::Italic | FontStyle::Oblique)
}
#[inline]
pub fn is_underline(&self) -> bool {
self.text_decoration_underline
}
#[inline]
pub fn is_strikethrough(&self) -> bool {
self.text_decoration_line_through
}
#[inline]
pub fn is_superscript(&self) -> bool {
self.vertical_align_super
}
#[inline]
pub fn is_subscript(&self) -> bool {
self.vertical_align_sub
}
pub fn is_monospace(&self) -> bool {
self.font_family
.as_ref()
.map(|f| {
let lower = f.to_lowercase();
lower.contains("mono")
|| lower.contains("courier")
|| lower.contains("consolas")
|| lower.contains("menlo")
})
.unwrap_or(false)
}
pub fn is_ordered_list(&self) -> bool {
matches!(
self.list_style_type,
ListStyleType::Decimal
| ListStyleType::LowerAlpha
| ListStyleType::UpperAlpha
| ListStyleType::LowerRoman
| ListStyleType::UpperRoman
)
}
#[inline]
pub fn is_small_caps(&self) -> bool {
matches!(self.font_variant, FontVariant::SmallCaps)
}
}
impl ToCss for ComputedStyle {
fn to_css(&self, buf: &mut String) {
let default = ComputedStyle::default();
if let Some(ref family) = self.font_family {
write!(buf, "font-family: {}; ", family).unwrap();
}
if self.font_size != default.font_size {
buf.push_str("font-size: ");
self.font_size.to_css(buf);
buf.push_str("; ");
}
if self.font_weight != default.font_weight {
buf.push_str("font-weight: ");
self.font_weight.to_css(buf);
buf.push_str("; ");
}
if self.font_style != default.font_style {
buf.push_str("font-style: ");
self.font_style.to_css(buf);
buf.push_str("; ");
}
if let Some(color) = self.color {
buf.push_str("color: ");
color.to_css(buf);
buf.push_str("; ");
}
if let Some(bg) = self.background_color {
buf.push_str("background-color: ");
bg.to_css(buf);
buf.push_str("; ");
}
if self.text_align != default.text_align {
buf.push_str("text-align: ");
self.text_align.to_css(buf);
buf.push_str("; ");
}
if self.text_indent != default.text_indent {
buf.push_str("text-indent: ");
self.text_indent.to_css(buf);
buf.push_str("; ");
}
if self.line_height != default.line_height {
buf.push_str("line-height: ");
self.line_height.to_css(buf);
buf.push_str("; ");
}
let mut decorations = Vec::new();
if self.text_decoration_underline {
decorations.push("underline");
}
if self.text_decoration_line_through {
decorations.push("line-through");
}
if !decorations.is_empty() {
write!(buf, "text-decoration: {}; ", decorations.join(" ")).unwrap();
}
if self.display != default.display {
buf.push_str("display: ");
self.display.to_css(buf);
buf.push_str("; ");
}
if self.margin_top != default.margin_top {
buf.push_str("margin-top: ");
self.margin_top.to_css(buf);
buf.push_str("; ");
}
if self.margin_bottom != default.margin_bottom {
buf.push_str("margin-bottom: ");
self.margin_bottom.to_css(buf);
buf.push_str("; ");
}
if self.margin_left != default.margin_left {
buf.push_str("margin-left: ");
self.margin_left.to_css(buf);
buf.push_str("; ");
}
if self.margin_right != default.margin_right {
buf.push_str("margin-right: ");
self.margin_right.to_css(buf);
buf.push_str("; ");
}
if self.padding_top != default.padding_top {
buf.push_str("padding-top: ");
self.padding_top.to_css(buf);
buf.push_str("; ");
}
if self.padding_bottom != default.padding_bottom {
buf.push_str("padding-bottom: ");
self.padding_bottom.to_css(buf);
buf.push_str("; ");
}
if self.padding_left != default.padding_left {
buf.push_str("padding-left: ");
self.padding_left.to_css(buf);
buf.push_str("; ");
}
if self.padding_right != default.padding_right {
buf.push_str("padding-right: ");
self.padding_right.to_css(buf);
buf.push_str("; ");
}
if self.vertical_align_super {
buf.push_str("vertical-align: super; ");
} else if self.vertical_align_sub {
buf.push_str("vertical-align: sub; ");
}
if self.list_style_type != default.list_style_type {
buf.push_str("list-style-type: ");
self.list_style_type.to_css(buf);
buf.push_str("; ");
}
if self.font_variant != FontVariant::Normal {
buf.push_str("font-variant: ");
self.font_variant.to_css(buf);
buf.push_str("; ");
}
if self.letter_spacing != default.letter_spacing {
buf.push_str("letter-spacing: ");
self.letter_spacing.to_css(buf);
buf.push_str("; ");
}
if self.word_spacing != default.word_spacing {
buf.push_str("word-spacing: ");
self.word_spacing.to_css(buf);
buf.push_str("; ");
}
if self.text_transform != default.text_transform {
buf.push_str("text-transform: ");
self.text_transform.to_css(buf);
buf.push_str("; ");
}
if self.hyphens != default.hyphens {
buf.push_str("hyphens: ");
self.hyphens.to_css(buf);
buf.push_str("; ");
}
if self.no_break {
buf.push_str("white-space: nowrap; ");
}
if self.underline_style != default.underline_style {
buf.push_str("text-decoration-style: ");
self.underline_style.to_css(buf);
buf.push_str("; ");
}
if self.overline {
buf.push_str("text-decoration-line: overline; ");
}
if let Some(color) = self.underline_color {
buf.push_str("text-decoration-color: ");
color.to_css(buf);
buf.push_str("; ");
}
if self.width != default.width {
buf.push_str("width: ");
self.width.to_css(buf);
buf.push_str("; ");
}
if self.height != default.height {
buf.push_str("height: ");
self.height.to_css(buf);
buf.push_str("; ");
}
if self.max_width != default.max_width {
buf.push_str("max-width: ");
self.max_width.to_css(buf);
buf.push_str("; ");
}
if self.min_height != default.min_height {
buf.push_str("min-height: ");
self.min_height.to_css(buf);
buf.push_str("; ");
}
if self.float != default.float {
buf.push_str("float: ");
self.float.to_css(buf);
buf.push_str("; ");
}
if self.break_before != default.break_before {
buf.push_str("break-before: ");
self.break_before.to_css(buf);
buf.push_str("; ");
}
if self.break_after != default.break_after {
buf.push_str("break-after: ");
self.break_after.to_css(buf);
buf.push_str("; ");
}
if self.break_inside != default.break_inside {
buf.push_str("break-inside: ");
self.break_inside.to_css(buf);
buf.push_str("; ");
}
if self.border_style_top != default.border_style_top {
buf.push_str("border-top-style: ");
self.border_style_top.to_css(buf);
buf.push_str("; ");
}
if self.border_style_right != default.border_style_right {
buf.push_str("border-right-style: ");
self.border_style_right.to_css(buf);
buf.push_str("; ");
}
if self.border_style_bottom != default.border_style_bottom {
buf.push_str("border-bottom-style: ");
self.border_style_bottom.to_css(buf);
buf.push_str("; ");
}
if self.border_style_left != default.border_style_left {
buf.push_str("border-left-style: ");
self.border_style_left.to_css(buf);
buf.push_str("; ");
}
if self.border_width_top != default.border_width_top {
buf.push_str("border-top-width: ");
self.border_width_top.to_css(buf);
buf.push_str("; ");
}
if self.border_width_right != default.border_width_right {
buf.push_str("border-right-width: ");
self.border_width_right.to_css(buf);
buf.push_str("; ");
}
if self.border_width_bottom != default.border_width_bottom {
buf.push_str("border-bottom-width: ");
self.border_width_bottom.to_css(buf);
buf.push_str("; ");
}
if self.border_width_left != default.border_width_left {
buf.push_str("border-left-width: ");
self.border_width_left.to_css(buf);
buf.push_str("; ");
}
if let Some(color) = self.border_color_top {
buf.push_str("border-top-color: ");
color.to_css(buf);
buf.push_str("; ");
}
if let Some(color) = self.border_color_right {
buf.push_str("border-right-color: ");
color.to_css(buf);
buf.push_str("; ");
}
if let Some(color) = self.border_color_bottom {
buf.push_str("border-bottom-color: ");
color.to_css(buf);
buf.push_str("; ");
}
if let Some(color) = self.border_color_left {
buf.push_str("border-left-color: ");
color.to_css(buf);
buf.push_str("; ");
}
if self.border_radius_top_left != default.border_radius_top_left {
buf.push_str("border-top-left-radius: ");
self.border_radius_top_left.to_css(buf);
buf.push_str("; ");
}
if self.border_radius_top_right != default.border_radius_top_right {
buf.push_str("border-top-right-radius: ");
self.border_radius_top_right.to_css(buf);
buf.push_str("; ");
}
if self.border_radius_bottom_left != default.border_radius_bottom_left {
buf.push_str("border-bottom-left-radius: ");
self.border_radius_bottom_left.to_css(buf);
buf.push_str("; ");
}
if self.border_radius_bottom_right != default.border_radius_bottom_right {
buf.push_str("border-bottom-right-radius: ");
self.border_radius_bottom_right.to_css(buf);
buf.push_str("; ");
}
if self.list_style_position != default.list_style_position {
buf.push_str("list-style-position: ");
self.list_style_position.to_css(buf);
buf.push_str("; ");
}
if self.visibility != default.visibility {
buf.push_str("visibility: ");
self.visibility.to_css(buf);
buf.push_str("; ");
}
}
}
#[derive(Clone)]
pub struct StylePool {
styles: Vec<ComputedStyle>,
intern_map: HashMap<ComputedStyle, StyleId>,
}
impl Default for StylePool {
fn default() -> Self {
Self::new()
}
}
impl StylePool {
pub fn new() -> Self {
let default_style = ComputedStyle::default();
let mut intern_map = HashMap::new();
intern_map.insert(default_style.clone(), StyleId::DEFAULT);
Self {
styles: vec![default_style],
intern_map,
}
}
pub fn intern(&mut self, style: ComputedStyle) -> StyleId {
if let Some(&id) = self.intern_map.get(&style) {
return id;
}
let id = StyleId(self.styles.len() as u32);
self.intern_map.insert(style.clone(), id);
self.styles.push(style);
id
}
pub fn get(&self, id: StyleId) -> Option<&ComputedStyle> {
self.styles.get(id.0 as usize)
}
pub fn len(&self) -> usize {
self.styles.len()
}
pub fn is_empty(&self) -> bool {
self.styles.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (StyleId, &ComputedStyle)> {
self.styles
.iter()
.enumerate()
.map(|(i, s)| (StyleId(i as u32), s))
}
}
impl std::fmt::Debug for StylePool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StylePool")
.field("count", &self.styles.len())
.finish()
}
}
#[cfg(test)]
#[allow(clippy::field_reassign_with_default)]
mod tests {
use super::*;
#[test]
fn test_color_to_css_opaque() {
assert_eq!(Color::BLACK.to_css_string(), "#000000");
assert_eq!(Color::WHITE.to_css_string(), "#ffffff");
assert_eq!(Color::rgb(255, 0, 0).to_css_string(), "#ff0000");
assert_eq!(Color::rgb(0, 128, 255).to_css_string(), "#0080ff");
}
#[test]
fn test_color_to_css_transparent() {
assert_eq!(Color::TRANSPARENT.to_css_string(), "transparent");
}
#[test]
fn test_color_to_css_alpha() {
let color = Color::rgba(255, 0, 0, 128);
let css = color.to_css_string();
assert!(css.starts_with("rgba(255,0,0,"));
assert!(css.contains("0.50")); }
#[test]
fn test_length_to_css() {
assert_eq!(Length::Auto.to_css_string(), "auto");
assert_eq!(Length::Px(0.0).to_css_string(), "0");
assert_eq!(Length::Px(16.0).to_css_string(), "16px");
assert_eq!(Length::Em(1.5).to_css_string(), "1.5em");
assert_eq!(Length::Rem(2.0).to_css_string(), "2rem");
assert_eq!(Length::Percent(50.0).to_css_string(), "50%");
}
#[test]
fn test_font_weight_to_css() {
assert_eq!(FontWeight::NORMAL.to_css_string(), "normal");
assert_eq!(FontWeight::BOLD.to_css_string(), "bold");
assert_eq!(FontWeight(300).to_css_string(), "300");
assert_eq!(FontWeight(600).to_css_string(), "600");
}
#[test]
fn test_font_style_to_css() {
assert_eq!(FontStyle::Normal.to_css_string(), "normal");
assert_eq!(FontStyle::Italic.to_css_string(), "italic");
assert_eq!(FontStyle::Oblique.to_css_string(), "oblique");
}
#[test]
fn test_text_align_to_css() {
assert_eq!(TextAlign::Left.to_css_string(), "left");
assert_eq!(TextAlign::Center.to_css_string(), "center");
assert_eq!(TextAlign::Justify.to_css_string(), "justify");
}
#[test]
fn test_display_to_css() {
assert_eq!(Display::Block.to_css_string(), "block");
assert_eq!(Display::Inline.to_css_string(), "inline");
assert_eq!(Display::None.to_css_string(), "none");
}
#[test]
fn test_computed_style_to_css_default() {
let style = ComputedStyle::default();
assert_eq!(style.to_css_string(), "");
}
#[test]
fn test_computed_style_to_css_bold() {
let mut style = ComputedStyle::default();
style.font_weight = FontWeight::BOLD;
let css = style.to_css_string();
assert!(css.contains("font-weight: bold;"));
}
#[test]
fn test_computed_style_to_css_multiple() {
let mut style = ComputedStyle::default();
style.font_weight = FontWeight::BOLD;
style.font_style = FontStyle::Italic;
style.color = Some(Color::rgb(255, 0, 0));
style.text_align = TextAlign::Center;
let css = style.to_css_string();
assert!(css.contains("font-weight: bold;"));
assert!(css.contains("font-style: italic;"));
assert!(css.contains("color: #ff0000;"));
assert!(css.contains("text-align: center;"));
}
#[test]
fn test_computed_style_to_css_decorations() {
let mut style = ComputedStyle::default();
style.text_decoration_underline = true;
style.text_decoration_line_through = true;
let css = style.to_css_string();
assert!(css.contains("text-decoration: underline line-through;"));
}
#[test]
fn test_style_pool_interning() {
let mut pool = StylePool::new();
let mut style1 = ComputedStyle::default();
style1.font_weight = FontWeight::BOLD;
let id1 = pool.intern(style1.clone());
let id2 = pool.intern(style1);
assert_eq!(id1, id2);
assert_eq!(pool.len(), 2); }
#[test]
fn test_style_pool_iter() {
let mut pool = StylePool::new();
let mut style = ComputedStyle::default();
style.font_weight = FontWeight::BOLD;
pool.intern(style);
let ids: Vec<StyleId> = pool.iter().map(|(id, _)| id).collect();
assert_eq!(ids, vec![StyleId(0), StyleId(1)]);
}
}