///|
/// Apply inheritance for inherited properties not in cascaded values
fn apply_inheritance(
builder : StyleBuilder,
cascaded : @cascade.CascadedValues,
ctx : ComputeContext,
) -> Unit {
// List of inherited properties we support
let inherited_props = [
"direction", "writing-mode", "text-align", "line-height", "font-size", "font-family",
"font-weight", "font-style", "color", "visibility", "white-space", "pointer-events",
]
for prop in inherited_props {
let covered_by_font_shorthand = cascaded.has("font") &&
(
prop == "font-size" ||
prop == "line-height" ||
prop == "font-family" ||
prop == "font-weight"
)
// If property not in cascaded values, inherit from parent
if !cascaded.has(prop) && !covered_by_font_shorthand {
match ctx.parent_style {
Some(parent) => {
let value = get_style_value_as_string(prop, parent)
apply_property(builder, prop, value, ctx)
}
None => ()
}
}
}
}
///|
/// Compute style from inline style string (raw, without box-sizing adjustment)
/// Use this when merging individual properties into an existing style.
/// The caller is responsible for calling adjust_for_box_sizing on the final result.
pub fn compute_inline_raw(
inline_css : String,
ctx : ComputeContext,
) -> @style.Style {
// Parse inline style to declarations
let decls : Array[@cascade.Declaration] = []
// Simple parsing: split by semicolon
let pairs = inline_css.split(";")
let mut order = 0
for pair in pairs {
let pair_str = pair.to_owned().trim()
if pair_str.is_empty() {
continue
}
// Find colon
let mut colon_pos = -1
for i = 0; i < pair_str.length(); i = i + 1 {
if pair_str[i].to_int().unsafe_to_char() == ':' {
colon_pos = i
break
}
}
if colon_pos > 0 {
let prop = view_to_string(pair_str.view(end_offset=colon_pos)).trim()
let val = view_to_string(pair_str.view(start_offset=colon_pos + 1)).trim()
if !prop.is_empty() &&
(!val.is_empty() || starts_with(prop.to_owned(), "--")) {
let (value, importance) = strip_inline_important_marker(val.to_owned())
decls.push(
@cascade.Declaration::with_metadata(
prop.to_owned(),
Value(value),
Author,
importance,
{ a: 1000, b: 0, c: 0 },
order,
),
)
order += 1
}
}
}
// Cascade
let cascaded = @cascade.cascade(decls)
// Compute (without box-sizing adjustment)
compute(cascaded, ctx)
}
///|
/// Compute style from inline style string
/// This is the standard function that applies box-sizing adjustment.
pub fn compute_inline(
inline_css : String,
ctx : ComputeContext,
) -> @style.Style {
let style = compute_inline_raw(inline_css, ctx)
// Convert content-box dimensions to border-box dimensions
// The layout engine always works with outer (border-box) dimensions
adjust_for_box_sizing(style)
}
///|
/// Apply a single CSS property to an existing style directly
/// This skips the CSS string parsing step for better performance
pub fn apply_property_direct(
style : @style.Style,
property : String,
value : String,
ctx : ComputeContext,
) -> @style.Style {
// Special handling for font-size: when font-size changes,
// update line-height proportionally to maintain the ratio
if property == "font-size" {
let builder = StyleBuilder::from_style(style)
let old_fs = style.font_size
apply_property(builder, property, value, ctx)
let new_fs = builder.font_size
// Maintain line-height / font-size ratio
let lh_ratio = if old_fs > 0.0 { style.line_height / old_fs } else { 1.0 }
builder.line_height = new_fs * lh_ratio
return builder.build()
}
let builder = StyleBuilder::from_style(style)
apply_property(builder, property, value, ctx)
builder.build()
}
///|
/// Adjust dimensions for box-sizing: content-box
/// When box-sizing is content-box (default), specified width/height are content dimensions.
/// The layout engine expects outer dimensions (border-box), so we adjust here.
fn adjust_for_box_sizing(style : @style.Style) -> @style.Style {
match style.box_sizing {
BorderBox => style // No adjustment needed
ContentBox => {
// Calculate padding and border sums
let padding_h = resolve_dimension_to_px(style.padding.left) +
resolve_dimension_to_px(style.padding.right)
let padding_v = resolve_dimension_to_px(style.padding.top) +
resolve_dimension_to_px(style.padding.bottom)
let border_h = resolve_dimension_to_px(style.border.left) +
resolve_dimension_to_px(style.border.right)
let border_v = resolve_dimension_to_px(style.border.top) +
resolve_dimension_to_px(style.border.bottom)
// Adjust width and height to include padding+border
let adjusted_width = match style.width {
Length(w) => @types.Dimension::Length(w + padding_h + border_h)
other => other
}
let adjusted_height = match style.height {
Length(h) => @types.Dimension::Length(h + padding_v + border_v)
other => other
}
// Adjust min/max constraints too
let adjusted_min_width = match style.min_width {
Length(w) => @types.Dimension::Length(w + padding_h + border_h)
other => other
}
let adjusted_min_height = match style.min_height {
Length(h) => @types.Dimension::Length(h + padding_v + border_v)
other => other
}
let adjusted_max_width = match style.max_width {
Length(w) => @types.Dimension::Length(w + padding_h + border_h)
other => other
}
let adjusted_max_height = match style.max_height {
Length(h) => @types.Dimension::Length(h + padding_v + border_v)
other => other
}
{
..style,
width: adjusted_width,
height: adjusted_height,
min_width: adjusted_min_width,
min_height: adjusted_min_height,
max_width: adjusted_max_width,
max_height: adjusted_max_height,
// Mark as adjusted (now using border-box semantics internally)
box_sizing: BorderBox,
}
}
}
}
///|
/// Helper to resolve a dimension to pixels (for padding/border calculation)
fn resolve_dimension_to_px(dim : @types.Dimension) -> Double {
match dim {
Length(v) => v
Percent(_) => 0.0 // Percentages are resolved later
Calc(px, _) => px // Percent component is resolved later
MathFn(op, args) =>
// Percent components resolve later; reduce using px parts only.
if args.is_empty() {
0.0
} else {
@types.apply_math_op(op, args.map(fn(a) { a.0 }))
}
Auto => 0.0
MinContent => 0.0 // Intrinsic sizing resolved during layout
MaxContent => 0.0
FitContent(_) => 0.0
}
}