///|
priv struct StyleDeclaration {
name : String
value : String
}
///|
priv enum StyleChoice {
StyleInitial
StyleInherited
StyleSpecified(String)
}
///|
fn is_custom_property(name : String) -> Bool {
name.length() >= 3 && name[0] == '-' && name[1] == '-'
}
///|
fn find_winning_declaration(
declarations : Array[StyleDeclaration],
name : String,
) -> StyleDeclaration? {
for declaration in declarations {
if declaration.name == name {
return Some(declaration)
}
}
None
}
///|
fn resolved_noncomputed_value(
declarations : Array[StyleDeclaration],
properties : Map[String, String],
name : String,
) -> String? {
match find_winning_declaration(declarations, name) {
Some(declaration) =>
match @css.resolve_custom_property_value(declaration.value, properties) {
Some(value) =>
match trim_string(value).to_lower() {
"inherit" | "initial" | "unset" | "revert" | "revert-layer" => None
_ =>
if svg_property_value_is_valid(name, value) {
Some(trim_string(value))
} else {
None
}
}
None => None
}
None => None
}
}
///|
fn resolved_element_noncomputed_value(
attrs : Array[(String, String)],
declarations : Array[StyleDeclaration],
properties : Map[String, String],
name : String,
) -> String? {
match resolved_noncomputed_value(declarations, properties, name) {
Some(value) => Some(value)
None =>
if find_winning_declaration(declarations, name) is Some(_) {
None
} else {
get_attr(attrs, name)
}
}
}
///|
fn declarations_are_same(
left : StyleDeclaration,
right : StyleDeclaration,
) -> Bool {
left.name == right.name && left.value == right.value
}
///|
fn resolve_style_choice(
declarations : Array[StyleDeclaration],
properties : Map[String, String],
property : ComputedStyleProperty,
) -> StyleChoice {
let default_choice = if property.is_inherited() {
StyleInherited
} else {
StyleInitial
}
match find_winning_declaration(declarations, property.name()) {
Some(declaration) =>
match @css.resolve_custom_property_value(declaration.value, properties) {
Some(resolved) =>
match trim_string(resolved).to_lower() {
"inherit" => StyleInherited
"initial" => StyleInitial
"unset" => default_choice
"revert" | "revert-layer" => default_choice
_ =>
if svg_property_value_is_valid(property.name(), resolved) {
StyleSpecified(trim_string(resolved))
} else {
default_choice
}
}
None => default_choice
}
None => default_choice
}
}
///|
fn apply_resolved_noncomputed_attributes(
node : SVGNode,
attrs : Array[(String, String)],
declarations : Array[StyleDeclaration],
properties : Map[String, String],
length_context : LengthContext,
) -> Unit {
for attr in attrs {
let name = attr.0
if name != "style" && !is_computed_style_property(name.to_lower()) {
match @css.resolve_custom_property_value(attr.1, properties) {
Some(value) if svg_property_value_is_valid(name, value) =>
apply_attribute(node, name, value, length_context~)
Some(_) | None => ()
}
}
}
for declaration in declarations {
if !is_custom_property(declaration.name) &&
!is_computed_style_property(declaration.name) {
match find_winning_declaration(declarations, declaration.name) {
Some(winner) if declarations_are_same(winner, declaration) =>
match @css.resolve_custom_property_value(winner.value, properties) {
Some(value) if svg_property_value_is_valid(winner.name, value) =>
apply_attribute(node, winner.name, value, length_context~)
Some(_) | None =>
apply_initial_noncomputed_attribute(node, winner.name)
}
_ => ()
}
}
}
}
///|
fn apply_initial_noncomputed_attribute(node : SVGNode, name : String) -> Unit {
match name {
"x"
| "y"
| "width"
| "height"
| "rx"
| "ry"
| "cx"
| "cy"
| "r"
| "x1"
| "y1"
| "x2"
| "y2" => apply_attribute(node, name, "0")
"d" | "points" => apply_attribute(node, name, "")
"transform" => node.transform = Transform::identity()
"clip-path" => node.clear_clip_path()
"mask" => node.clear_mask()
"filter" => node.filter_graph_id = None
"mix-blend-mode" => node.blend_mode = Normal
"isolation" => node.isolation = Auto
_ => ()
}
}
///|
fn clamp_svg_opacity(value : String) -> Double {
let parsed = parse_number(value)
if parsed < 0.0 {
0.0
} else if parsed > 1.0 {
1.0
} else {
parsed
}
}
///|
fn resolve_computed_style(
node : SVGNode,
attrs : Array[(String, String)],
cascaded : @css_cascade.CascadedValues,
parent : InheritedStyle,
apply_noncomputed : Bool,
length_viewport : (Double, Double),
css_viewport : (Double, Double),
is_root? : Bool = false,
stylesheets? : Array[@css_cascade.Stylesheet] = [],
sample_time_seconds? : Double = 0.0,
) -> InheritedStyle {
let compute_context = {
..@css_computed.ComputeContext::with_parent_and_vars(
parent.css_style,
parent.custom_properties,
),
root_font_size: parent.root_font_size,
font_size: parent.css_style.font_size,
viewport_width: css_viewport.0,
viewport_height: css_viewport.1,
}
let (css_style, custom_property_map) = @css.compute_style_with_vars(
cascaded, compute_context,
)
let base_declarations = cascaded_style_declarations(cascaded)
let custom_properties = custom_property_map
let root_font_size = if is_root {
css_style.font_size
} else {
parent.root_font_size
}
let length_context = LengthContext::new(
length_viewport.0,
length_viewport.1,
font_size=css_style.font_size,
root_font_size~,
css_viewport_width=css_viewport.0,
css_viewport_height=css_viewport.1,
)
let animation_declarations = sampled_animation_declarations(
css_style, stylesheets, sample_time_seconds, custom_properties, length_context,
base_declarations, attrs, parent,
)
let declarations : Array[StyleDeclaration] = []
for declaration in animation_declarations {
let important = match cascaded.get(declaration.name) {
Some(winner) => winner.importance is Important
None => false
}
if !important {
declarations.push(declaration)
}
}
for declaration in base_declarations {
declarations.push(declaration)
}
node.fill = parent.fill
node.fill_rule = parent.fill_rule
node.clip_rule = parent.clip_rule
node.fill_opacity = parent.fill_opacity
node.stroke = parent.stroke
node.stroke_opacity = parent.stroke_opacity
node.color = Some(parent.color)
node.paint_order = parent.paint_order
node.opacity = css_style.opacity
node.marker_start = parent.marker_start
node.marker_mid = parent.marker_mid
node.marker_end = parent.marker_end
node.image_sampling = parent.image_sampling
let mut stop_color = parent.stop_color
let mut stop_opacity = parent.stop_opacity
if apply_noncomputed {
apply_resolved_noncomputed_attributes(
node, attrs, declarations, custom_properties, length_context,
)
}
match resolve_style_choice(declarations, custom_properties, StyleFill) {
StyleInitial => node.fill = SolidColor(Color::black())
StyleInherited => ()
StyleSpecified(value) => node.fill = parse_paint(value)
}
match resolve_style_choice(declarations, custom_properties, StyleFillRule) {
StyleInitial => node.fill_rule = NonZero
StyleInherited => ()
StyleSpecified(value) => node.fill_rule = parse_fill_rule(value)
}
match resolve_style_choice(declarations, custom_properties, StyleClipRule) {
StyleInitial => node.clip_rule = NonZero
StyleInherited => ()
StyleSpecified(value) => node.clip_rule = parse_fill_rule(value)
}
match
resolve_style_choice(declarations, custom_properties, StyleFillOpacity) {
StyleInitial => node.fill_opacity = 1.0
StyleInherited => ()
StyleSpecified(value) => node.fill_opacity = clamp_svg_opacity(value)
}
match resolve_style_choice(declarations, custom_properties, StyleStroke) {
StyleInitial => node.stroke = { ..node.stroke, paint: None }
StyleInherited => ()
StyleSpecified(value) =>
node.stroke = { ..node.stroke, paint: parse_paint(value) }
}
match
resolve_style_choice(declarations, custom_properties, StyleStrokeWidth) {
StyleInitial => node.stroke = { ..node.stroke, width: 1.0 }
StyleInherited => ()
StyleSpecified(value) =>
node.stroke = {
..node.stroke,
width: parse_length_with_context(value, Diagonal, length_context),
}
}
match
resolve_style_choice(declarations, custom_properties, StyleStrokeLinecap) {
StyleInitial => node.stroke = { ..node.stroke, linecap: Butt }
StyleInherited => ()
StyleSpecified(value) =>
node.stroke = { ..node.stroke, linecap: parse_linecap(value) }
}
match
resolve_style_choice(declarations, custom_properties, StyleStrokeLinejoin) {
StyleInitial => node.stroke = { ..node.stroke, linejoin: Miter }
StyleInherited => ()
StyleSpecified(value) =>
node.stroke = { ..node.stroke, linejoin: parse_linejoin(value) }
}
match
resolve_style_choice(declarations, custom_properties, StyleStrokeMiterlimit) {
StyleInitial => node.stroke = { ..node.stroke, miterlimit: 4.0 }
StyleInherited => ()
StyleSpecified(value) =>
node.stroke = { ..node.stroke, miterlimit: parse_number(value) }
}
match resolve_style_choice(declarations, custom_properties, StyleColor) {
StyleInitial => node.color = Some(Color::black())
StyleInherited => ()
StyleSpecified(value) =>
if value.to_lower() == "currentcolor" {
node.color = Some(parent.color)
} else {
node.color = Some(parse_color(value))
}
}
match resolve_style_choice(declarations, custom_properties, StyleOpacity) {
StyleInitial => node.opacity = 1.0
StyleInherited => node.opacity = parent.css_style.opacity
StyleSpecified(value) => node.opacity = clamp_svg_opacity(value)
}
match resolve_style_choice(declarations, custom_properties, StyleStopColor) {
StyleInitial => stop_color = Color::black()
StyleInherited => ()
StyleSpecified(value) =>
if value.to_lower() == "currentcolor" {
stop_color = node.color.unwrap_or(Color::black())
} else {
stop_color = parse_color(value)
}
}
match
resolve_style_choice(declarations, custom_properties, StyleStopOpacity) {
StyleInitial => stop_opacity = 1.0
StyleInherited => ()
StyleSpecified(value) => stop_opacity = clamp_svg_opacity(value)
}
match
resolve_style_choice(declarations, custom_properties, StyleStrokeDasharray) {
StyleInitial => node.stroke = { ..node.stroke, dasharray: None }
StyleInherited => ()
StyleSpecified(value) =>
node.stroke = {
..node.stroke,
dasharray: parse_dasharray(value, length_context~),
}
}
match
resolve_style_choice(declarations, custom_properties, StyleStrokeDashoffset) {
StyleInitial => node.stroke = { ..node.stroke, dashoffset: 0.0 }
StyleInherited => ()
StyleSpecified(value) =>
node.stroke = {
..node.stroke,
dashoffset: parse_length_with_context(value, Diagonal, length_context),
}
}
match
resolve_style_choice(declarations, custom_properties, StyleStrokeOpacity) {
StyleInitial => node.stroke_opacity = 1.0
StyleInherited => ()
StyleSpecified(value) => node.stroke_opacity = clamp_svg_opacity(value)
}
match
resolve_style_choice(declarations, custom_properties, StyleVectorEffect) {
StyleInitial => node.stroke = { ..node.stroke, non_scaling: false }
StyleInherited => ()
StyleSpecified(value) =>
node.stroke = {
..node.stroke,
non_scaling: trim_string(value).to_lower() == "non-scaling-stroke",
}
}
match
resolve_style_choice(declarations, custom_properties, StyleImageRendering) {
StyleInitial => node.image_sampling = Bilinear
StyleInherited => ()
StyleSpecified(value) => node.image_sampling = parse_image_sampling(value)
}
match resolve_style_choice(declarations, custom_properties, StylePaintOrder) {
StyleInitial => node.paint_order = PaintOrder::default()
StyleInherited => ()
StyleSpecified(value) => node.paint_order = parse_paint_order(value)
}
match
resolve_style_choice(declarations, custom_properties, StyleMarkerStart) {
StyleInitial => node.marker_start = None
StyleInherited => ()
StyleSpecified(value) => node.marker_start = parse_marker_ref(value)
}
match resolve_style_choice(declarations, custom_properties, StyleMarkerMid) {
StyleInitial => node.marker_mid = None
StyleInherited => ()
StyleSpecified(value) => node.marker_mid = parse_marker_ref(value)
}
match resolve_style_choice(declarations, custom_properties, StyleMarkerEnd) {
StyleInitial => node.marker_end = None
StyleInherited => ()
StyleSpecified(value) => node.marker_end = parse_marker_ref(value)
}
node.fill_is_set = true
node.stroke_paint_is_set = true
node.stroke_width_is_set = true
node.color_is_set = true
node.marker_start_is_set = true
node.marker_mid_is_set = true
node.marker_end_is_set = true
inherited_style_from_node(
node, css_style, custom_property_map, stop_color, stop_opacity, root_font_size,
)
}