use azul_core::{
dom::{NodeId, NodeType},
geom::LogicalSize,
id::NodeId as CoreNodeId,
styled_dom::{StyledDom, StyledNodeState},
};
use azul_css::{
css::CssPropertyValue,
props::{
basic::{
font::{StyleFontFamily, StyleFontFamilyVec, StyleFontWeight, StyleFontStyle},
pixel::{DEFAULT_FONT_SIZE, PT_TO_PX},
ColorU, PhysicalSize, PixelValue, PropertyContext, ResolutionContext,
},
layout::{
BoxDecorationBreak, BreakInside, LayoutBoxSizing, LayoutClear, LayoutDisplay,
LayoutFlexDirection, LayoutFlexWrap, LayoutFloat, LayoutHeight,
LayoutJustifyContent, LayoutAlignItems, LayoutAlignContent, LayoutOverflow,
LayoutPosition, LayoutWidth, LayoutWritingMode, Orphans, PageBreak, Widows,
StyleScrollbarGutter, StyleOverflowClipMargin,
grid::GridTemplateAreas,
},
property::{CssProperty, CssPropertyType,
LayoutFlexBasisValue, LayoutFlexDirectionValue, LayoutFlexWrapValue,
LayoutFlexGrowValue, LayoutFlexShrinkValue,
LayoutAlignItemsValue, LayoutAlignSelfValue, LayoutAlignContentValue,
LayoutJustifyContentValue, LayoutJustifyItemsValue, LayoutJustifySelfValue,
LayoutGapValue,
LayoutGridTemplateColumnsValue, LayoutGridTemplateRowsValue,
LayoutGridAutoColumnsValue, LayoutGridAutoRowsValue,
LayoutGridAutoFlowValue, LayoutGridColumnValue, LayoutGridRowValue,
},
style::{
border_radius::StyleBorderRadius,
lists::{StyleListStylePosition, StyleListStyleType},
StyleDirection, StyleTextAlign, StyleUserSelect, StyleVerticalAlign,
StyleVisibility, StyleWhiteSpace,
StyleUnicodeBidi, StyleTextBoxTrim, StyleTextBoxEdge,
StyleDominantBaseline, StyleAlignmentBaseline,
StyleInitialLetterAlign, StyleInitialLetterWrap,
},
},
};
use crate::{
font_traits::{ParsedFontTrait, StyleProperties},
solver3::{
display_list::{BorderRadius, PhysicalSizeImport},
layout_tree::LayoutNode,
scrollbar::ScrollbarRequirements,
},
};
pub fn get_element_font_size(
styled_dom: &StyledDom,
dom_id: NodeId,
node_state: &StyledNodeState,
) -> f32 {
let _ = compute_all_font_sizes_px; resolve_font_size_slow(styled_dom, dom_id, node_state)
}
fn compute_all_font_sizes_px(styled_dom: &StyledDom) -> alloc::vec::Vec<f32> {
use azul_css::props::{
basic::length::SizeMetric,
property::{CssProperty, CssPropertyType},
};
let n = styled_dom.node_data.len();
let mut sizes = alloc::vec![DEFAULT_FONT_SIZE; n];
if n == 0 {
return sizes;
}
let data_container = styled_dom.node_data.as_container();
let state_container = styled_dom.styled_nodes.as_container();
let hierarchy = styled_dom.node_hierarchy.as_container();
let cache = &styled_dom.css_property_cache.ptr;
for idx in 0..n {
let dom_id = NodeId::new(idx);
if let Some(vec) = cache.computed_values.get(idx) {
if let Ok(cv_idx) =
vec.binary_search_by_key(&CssPropertyType::FontSize, |(k, _)| *k)
{
if let CssProperty::FontSize(css_val) = &vec[cv_idx].1.property {
if let Some(fs) = css_val.get_property() {
if fs.inner.metric == SizeMetric::Px {
sizes[idx] = fs.inner.number.get();
continue;
}
}
}
}
}
let parent_font_size = hierarchy
.get(dom_id)
.and_then(|node| node.parent_id())
.map(|p| sizes[p.index()])
.unwrap_or(DEFAULT_FONT_SIZE);
let root_font_size = sizes[0];
let Some(node_data) = data_container.internal.get(idx) else {
sizes[idx] = DEFAULT_FONT_SIZE;
continue;
};
let Some(styled) = state_container.internal.get(idx) else {
sizes[idx] = DEFAULT_FONT_SIZE;
continue;
};
let node_state = &styled.styled_node_state;
let mut fast_fs: Option<f32> = None;
let mut compact_said_inherit = false;
if node_state.is_normal() {
if let Some(ref cc) = cache.compact_cache {
let raw = cc.get_font_size_raw(idx);
if raw == azul_css::compact_cache::U32_SENTINEL
|| raw == azul_css::compact_cache::U32_INHERIT
|| raw == azul_css::compact_cache::U32_INITIAL
{
compact_said_inherit = true;
} else if let Some(pv) = azul_css::compact_cache::decode_pixel_value_u32(raw) {
if pv.metric == SizeMetric::Px {
fast_fs = Some(pv.number.get());
} else {
let context = ResolutionContext {
element_font_size: DEFAULT_FONT_SIZE,
parent_font_size,
root_font_size,
containing_block_size: PhysicalSize::new(0.0, 0.0),
element_size: None,
viewport_size: PhysicalSize::new(0.0, 0.0),
};
fast_fs = Some(pv.resolve_with_context(&context, PropertyContext::FontSize));
}
}
}
}
if let Some(fs) = fast_fs {
sizes[idx] = fs;
continue;
}
if compact_said_inherit {
sizes[idx] = parent_font_size;
continue;
}
let resolved = cache
.get_font_size(node_data, &dom_id, node_state)
.and_then(|v| v.get_property().cloned())
.map(|v| {
let context = ResolutionContext {
element_font_size: DEFAULT_FONT_SIZE,
parent_font_size,
root_font_size,
containing_block_size: PhysicalSize::new(0.0, 0.0),
element_size: None,
viewport_size: PhysicalSize::new(0.0, 0.0),
};
v.inner
.resolve_with_context(&context, PropertyContext::FontSize)
});
sizes[idx] = resolved.unwrap_or(DEFAULT_FONT_SIZE);
}
sizes
}
fn resolve_font_size_slow(
styled_dom: &StyledDom,
dom_id: NodeId,
node_state: &StyledNodeState,
) -> f32 {
let node_data = &styled_dom.node_data.as_container()[dom_id];
let cache = &styled_dom.css_property_cache.ptr;
if let Some(vec) = cache.computed_values.get(dom_id.index()) {
if let Ok(idx) = vec.binary_search_by_key(
&azul_css::props::property::CssPropertyType::FontSize,
|(k, _)| *k,
) {
if let azul_css::props::property::CssProperty::FontSize(css_val) = &vec[idx].1.property {
if let Some(fs) = css_val.get_property() {
if fs.inner.metric == azul_css::props::basic::length::SizeMetric::Px {
return fs.inner.number.get();
}
}
}
}
}
let parent_font_size = styled_dom
.node_hierarchy
.as_container()
.get(dom_id)
.and_then(|node| node.parent_id())
.map(|parent_id| resolve_font_size_slow(styled_dom, parent_id, node_state))
.unwrap_or(DEFAULT_FONT_SIZE);
let root_font_size = if dom_id == NodeId::new(0) {
DEFAULT_FONT_SIZE
} else {
resolve_font_size_slow(styled_dom, NodeId::new(0), node_state)
};
cache
.get_font_size(node_data, &dom_id, node_state)
.and_then(|v| v.get_property().cloned())
.map(|v| {
let context = ResolutionContext {
element_font_size: DEFAULT_FONT_SIZE,
parent_font_size,
root_font_size,
containing_block_size: PhysicalSize::new(0.0, 0.0),
element_size: None,
viewport_size: PhysicalSize::new(0.0, 0.0),
};
v.inner
.resolve_with_context(&context, PropertyContext::FontSize)
})
.unwrap_or(DEFAULT_FONT_SIZE)
}
pub fn get_parent_font_size(
styled_dom: &StyledDom,
dom_id: NodeId,
_node_state: &StyledNodeState, ) -> f32 {
styled_dom
.node_hierarchy
.as_container()
.get(dom_id)
.and_then(|node| node.parent_id())
.map(|parent_id| {
let parent_state = &styled_dom.styled_nodes.as_container()[parent_id].styled_node_state;
get_element_font_size(styled_dom, parent_id, parent_state)
})
.unwrap_or(azul_css::props::basic::pixel::DEFAULT_FONT_SIZE)
}
pub fn get_root_font_size(styled_dom: &StyledDom, _node_state: &StyledNodeState) -> f32 {
let root_id = NodeId::new(0);
let root_state = &styled_dom.styled_nodes.as_container()[root_id].styled_node_state;
get_element_font_size(styled_dom, root_id, root_state)
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum MultiValue<T> {
Auto,
Initial,
Inherit,
Exact(T),
}
impl<T> MultiValue<T> {
pub fn is_auto(&self) -> bool {
matches!(self, MultiValue::Auto)
}
pub fn is_exact(&self) -> bool {
matches!(self, MultiValue::Exact(_))
}
pub fn exact(self) -> Option<T> {
match self {
MultiValue::Exact(v) => Some(v),
_ => None,
}
}
pub fn unwrap_or(self, default: T) -> T {
match self {
MultiValue::Exact(v) => v,
_ => default,
}
}
pub fn unwrap_or_default(self) -> T
where
T: Default,
{
match self {
MultiValue::Exact(v) => v,
_ => T::default(),
}
}
pub fn map<U, F>(self, f: F) -> MultiValue<U>
where
F: FnOnce(T) -> U,
{
match self {
MultiValue::Exact(v) => MultiValue::Exact(f(v)),
MultiValue::Auto => MultiValue::Auto,
MultiValue::Initial => MultiValue::Initial,
MultiValue::Inherit => MultiValue::Inherit,
}
}
}
impl MultiValue<LayoutOverflow> {
pub fn is_clipped(&self) -> bool {
matches!(
self,
MultiValue::Exact(
LayoutOverflow::Hidden
| LayoutOverflow::Clip
| LayoutOverflow::Auto
| LayoutOverflow::Scroll
)
)
}
pub fn is_scroll(&self) -> bool {
matches!(
self,
MultiValue::Exact(LayoutOverflow::Scroll | LayoutOverflow::Auto)
)
}
pub fn is_auto_overflow(&self) -> bool {
matches!(self, MultiValue::Exact(LayoutOverflow::Auto))
}
pub fn is_hidden(&self) -> bool {
matches!(self, MultiValue::Exact(LayoutOverflow::Hidden))
}
pub fn is_hidden_or_clip(&self) -> bool {
matches!(
self,
MultiValue::Exact(LayoutOverflow::Hidden | LayoutOverflow::Clip)
)
}
pub fn is_scroll_explicit(&self) -> bool {
matches!(self, MultiValue::Exact(LayoutOverflow::Scroll))
}
pub fn is_clip(&self) -> bool {
matches!(self, MultiValue::Exact(LayoutOverflow::Clip))
}
pub fn is_visible_or_clip(&self) -> bool {
matches!(
self,
MultiValue::Exact(LayoutOverflow::Visible | LayoutOverflow::Clip)
)
}
pub fn resolve_computed(&self, other_axis: &MultiValue<LayoutOverflow>) -> MultiValue<LayoutOverflow> {
match (self, other_axis) {
(MultiValue::Exact(val), MultiValue::Exact(other)) => {
MultiValue::Exact(val.resolve_computed(*other))
}
_ => *self,
}
}
}
impl MultiValue<LayoutPosition> {
pub fn is_absolute_or_fixed(&self) -> bool {
matches!(
self,
MultiValue::Exact(LayoutPosition::Absolute | LayoutPosition::Fixed)
)
}
}
impl MultiValue<LayoutFloat> {
pub fn is_none(&self) -> bool {
matches!(
self,
MultiValue::Auto
| MultiValue::Initial
| MultiValue::Inherit
| MultiValue::Exact(LayoutFloat::None)
)
}
}
impl<T: Default> Default for MultiValue<T> {
fn default() -> Self {
MultiValue::Auto
}
}
macro_rules! get_css_property_pixel {
($fn_name:ident, $cache_method:ident, $ua_property:expr, compact_i16 = $compact_method:ident) => {
pub fn $fn_name(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> MultiValue<PixelValue> {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
let raw = cc.$compact_method(node_id.index());
if raw == azul_css::compact_cache::I16_AUTO {
return MultiValue::Auto;
}
if raw == azul_css::compact_cache::I16_INITIAL {
return MultiValue::Initial;
}
if raw < azul_css::compact_cache::I16_SENTINEL_THRESHOLD {
return MultiValue::Exact(PixelValue::px(raw as f32 / 10.0));
}
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
let author_css = styled_dom
.css_property_cache
.ptr
.$cache_method(node_data, &node_id, node_state);
if let Some(ref val) = author_css {
if val.is_auto() {
return MultiValue::Auto;
}
if let Some(exact) = val.get_property().copied() {
return MultiValue::Exact(exact.inner);
}
}
let ua_css = azul_core::ua_css::get_ua_property(&node_data.node_type, $ua_property);
if let Some(ua_prop) = ua_css {
if let Some(inner) = ua_prop.get_pixel_inner() {
return MultiValue::Exact(inner);
}
}
MultiValue::Initial
}
};
($fn_name:ident, $cache_method:ident, $ua_property:expr) => {
pub fn $fn_name(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> MultiValue<PixelValue> {
let node_data = &styled_dom.node_data.as_container()[node_id];
let author_css = styled_dom
.css_property_cache
.ptr
.$cache_method(node_data, &node_id, node_state);
if let Some(ref val) = author_css {
if val.is_auto() {
return MultiValue::Auto;
}
if let Some(exact) = val.get_property().copied() {
return MultiValue::Exact(exact.inner);
}
}
let ua_css = azul_core::ua_css::get_ua_property(&node_data.node_type, $ua_property);
if let Some(ua_prop) = ua_css {
if let Some(inner) = ua_prop.get_pixel_inner() {
return MultiValue::Exact(inner);
}
}
MultiValue::Initial
}
};
}
trait CssPropertyPixelInner {
fn get_pixel_inner(&self) -> Option<PixelValue>;
}
impl CssPropertyPixelInner for azul_css::props::property::CssProperty {
fn get_pixel_inner(&self) -> Option<PixelValue> {
match self {
CssProperty::Left(CssPropertyValue::Exact(v)) => Some(v.inner),
CssProperty::Right(CssPropertyValue::Exact(v)) => Some(v.inner),
CssProperty::Top(CssPropertyValue::Exact(v)) => Some(v.inner),
CssProperty::Bottom(CssPropertyValue::Exact(v)) => Some(v.inner),
CssProperty::MarginLeft(CssPropertyValue::Exact(v)) => Some(v.inner),
CssProperty::MarginRight(CssPropertyValue::Exact(v)) => Some(v.inner),
CssProperty::MarginTop(CssPropertyValue::Exact(v)) => Some(v.inner),
CssProperty::MarginBottom(CssPropertyValue::Exact(v)) => Some(v.inner),
CssProperty::PaddingLeft(CssPropertyValue::Exact(v)) => Some(v.inner),
CssProperty::PaddingRight(CssPropertyValue::Exact(v)) => Some(v.inner),
CssProperty::PaddingTop(CssPropertyValue::Exact(v)) => Some(v.inner),
CssProperty::PaddingBottom(CssPropertyValue::Exact(v)) => Some(v.inner),
_ => None,
}
}
}
macro_rules! get_css_property {
($fn_name:ident, $cache_method:ident, $return_type:ty, $ua_property:expr, compact = $compact_method:ident) => {
pub fn $fn_name(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> MultiValue<$return_type> {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
return MultiValue::Exact(cc.$compact_method(node_id.index()));
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
let author_css = styled_dom
.css_property_cache
.ptr
.$cache_method(node_data, &node_id, node_state);
if let Some(val) = author_css.and_then(|v| v.get_property().cloned()) {
return MultiValue::Exact(val);
}
let ua_css = azul_core::ua_css::get_ua_property(&node_data.node_type, $ua_property);
if let Some(ua_prop) = ua_css {
if let Some(val) = extract_property_value::<$return_type>(ua_prop) {
return MultiValue::Exact(val);
}
}
MultiValue::Auto
}
};
($fn_name:ident, $cache_method:ident, $return_type:ty, $ua_property:expr, compact_u32_dim = $compact_raw_method:ident, $px_variant:path, $auto_variant:path, $min_content_variant:path, $max_content_variant:path) => {
pub fn $fn_name(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> MultiValue<$return_type> {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
let raw = cc.$compact_raw_method(node_id.index());
match raw {
azul_css::compact_cache::U32_AUTO => return MultiValue::Auto,
azul_css::compact_cache::U32_INITIAL => return MultiValue::Initial,
azul_css::compact_cache::U32_NONE => return MultiValue::Auto,
azul_css::compact_cache::U32_MIN_CONTENT => return MultiValue::Exact($min_content_variant),
azul_css::compact_cache::U32_MAX_CONTENT => return MultiValue::Exact($max_content_variant),
azul_css::compact_cache::U32_SENTINEL | azul_css::compact_cache::U32_INHERIT => {
}
_ => {
if let Some(pv) = azul_css::compact_cache::decode_pixel_value_u32(raw) {
return MultiValue::Exact($px_variant(pv));
}
}
}
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
let author_css = styled_dom
.css_property_cache
.ptr
.$cache_method(node_data, &node_id, node_state);
if let Some(val) = author_css.and_then(|v| v.get_property().cloned()) {
return MultiValue::Exact(val);
}
let ua_css = azul_core::ua_css::get_ua_property(&node_data.node_type, $ua_property);
if let Some(ua_prop) = ua_css {
if let Some(val) = extract_property_value::<$return_type>(ua_prop) {
return MultiValue::Exact(val);
}
}
MultiValue::Auto
}
};
($fn_name:ident, $cache_method:ident, $return_type:ty, $ua_property:expr, compact_u32_struct = $compact_raw_method:ident) => {
pub fn $fn_name(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> MultiValue<$return_type> {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
let raw = cc.$compact_raw_method(node_id.index());
match raw {
azul_css::compact_cache::U32_AUTO | azul_css::compact_cache::U32_NONE => return MultiValue::Auto,
azul_css::compact_cache::U32_INITIAL => return MultiValue::Initial,
azul_css::compact_cache::U32_SENTINEL | azul_css::compact_cache::U32_INHERIT => {
}
_ => {
if let Some(pv) = azul_css::compact_cache::decode_pixel_value_u32(raw) {
return MultiValue::Exact(
<$return_type as azul_css::props::PixelValueTaker>::from_pixel_value(pv)
);
}
}
}
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
let author_css = styled_dom
.css_property_cache
.ptr
.$cache_method(node_data, &node_id, node_state);
if let Some(val) = author_css.and_then(|v| v.get_property().cloned()) {
return MultiValue::Exact(val);
}
let ua_css = azul_core::ua_css::get_ua_property(&node_data.node_type, $ua_property);
if let Some(ua_prop) = ua_css {
if let Some(val) = extract_property_value::<$return_type>(ua_prop) {
return MultiValue::Exact(val);
}
}
MultiValue::Auto
}
};
($fn_name:ident, $cache_method:ident, $return_type:ty, $ua_property:expr) => {
pub fn $fn_name(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> MultiValue<$return_type> {
let node_data = &styled_dom.node_data.as_container()[node_id];
let author_css = styled_dom
.css_property_cache
.ptr
.$cache_method(node_data, &node_id, node_state);
if let Some(val) = author_css.and_then(|v| v.get_property().cloned()) {
return MultiValue::Exact(val);
}
let ua_css = azul_core::ua_css::get_ua_property(&node_data.node_type, $ua_property);
if let Some(ua_prop) = ua_css {
if let Some(val) = extract_property_value::<$return_type>(ua_prop) {
return MultiValue::Exact(val);
}
}
MultiValue::Auto
}
};
}
trait ExtractPropertyValue<T> {
fn extract(&self) -> Option<T>;
}
fn extract_property_value<T>(prop: &azul_css::props::property::CssProperty) -> Option<T>
where
azul_css::props::property::CssProperty: ExtractPropertyValue<T>,
{
prop.extract()
}
impl ExtractPropertyValue<LayoutWidth> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutWidth> {
match self {
Self::Width(CssPropertyValue::Exact(v)) => Some(v.clone()),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutHeight> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutHeight> {
match self {
Self::Height(CssPropertyValue::Exact(v)) => Some(v.clone()),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutMinWidth> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutMinWidth> {
match self {
Self::MinWidth(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutMinHeight> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutMinHeight> {
match self {
Self::MinHeight(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutMaxWidth> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutMaxWidth> {
match self {
Self::MaxWidth(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutMaxHeight> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutMaxHeight> {
match self {
Self::MaxHeight(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutDisplay> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutDisplay> {
match self {
Self::Display(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutWritingMode> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutWritingMode> {
match self {
Self::WritingMode(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutFlexWrap> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutFlexWrap> {
match self {
Self::FlexWrap(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutJustifyContent> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutJustifyContent> {
match self {
Self::JustifyContent(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleTextAlign> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleTextAlign> {
match self {
Self::TextAlign(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutFloat> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutFloat> {
match self {
Self::Float(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutClear> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutClear> {
match self {
Self::Clear(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutOverflow> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutOverflow> {
match self {
Self::OverflowX(CssPropertyValue::Exact(v)) => Some(*v),
Self::OverflowY(CssPropertyValue::Exact(v)) => Some(*v),
Self::OverflowBlock(CssPropertyValue::Exact(v)) => Some(*v),
Self::OverflowInline(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutPosition> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutPosition> {
match self {
Self::Position(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutBoxSizing> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutBoxSizing> {
match self {
Self::BoxSizing(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<PixelValue> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<PixelValue> {
self.get_pixel_inner()
}
}
impl ExtractPropertyValue<LayoutFlexDirection> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutFlexDirection> {
match self {
Self::FlexDirection(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutAlignItems> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutAlignItems> {
match self {
Self::AlignItems(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutAlignContent> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<LayoutAlignContent> {
match self {
Self::AlignContent(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleFontWeight> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleFontWeight> {
match self {
Self::FontWeight(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleFontStyle> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleFontStyle> {
match self {
Self::FontStyle(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleVisibility> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleVisibility> {
match self {
Self::Visibility(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleWhiteSpace> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleWhiteSpace> {
match self {
Self::WhiteSpace(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleDirection> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleDirection> {
match self {
Self::Direction(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleUnicodeBidi> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleUnicodeBidi> {
match self {
Self::UnicodeBidi(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleTextBoxTrim> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleTextBoxTrim> {
match self {
Self::TextBoxTrim(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleTextBoxEdge> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleTextBoxEdge> {
match self {
Self::TextBoxEdge(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleDominantBaseline> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleDominantBaseline> {
match self {
Self::DominantBaseline(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleAlignmentBaseline> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleAlignmentBaseline> {
match self {
Self::AlignmentBaseline(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleInitialLetterAlign> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleInitialLetterAlign> {
match self {
Self::InitialLetterAlign(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleInitialLetterWrap> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleInitialLetterWrap> {
match self {
Self::InitialLetterWrap(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleScrollbarGutter> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleScrollbarGutter> {
match self {
Self::ScrollbarGutter(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleOverflowClipMargin> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleOverflowClipMargin> {
match self {
Self::OverflowClipMargin(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleVerticalAlign> for azul_css::props::property::CssProperty {
fn extract(&self) -> Option<StyleVerticalAlign> {
match self {
Self::VerticalAlign(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
get_css_property!(
get_writing_mode,
get_writing_mode,
LayoutWritingMode,
azul_css::props::property::CssPropertyType::WritingMode,
compact = get_writing_mode
);
get_css_property!(
get_css_width,
get_width,
LayoutWidth,
azul_css::props::property::CssPropertyType::Width,
compact_u32_dim = get_width_raw, LayoutWidth::Px, LayoutWidth::Auto, LayoutWidth::MinContent, LayoutWidth::MaxContent
);
get_css_property!(
get_css_height,
get_height,
LayoutHeight,
azul_css::props::property::CssPropertyType::Height,
compact_u32_dim = get_height_raw, LayoutHeight::Px, LayoutHeight::Auto, LayoutHeight::MinContent, LayoutHeight::MaxContent
);
get_css_property!(
get_wrap,
get_flex_wrap,
LayoutFlexWrap,
azul_css::props::property::CssPropertyType::FlexWrap,
compact = get_flex_wrap
);
get_css_property!(
get_justify_content,
get_justify_content,
LayoutJustifyContent,
azul_css::props::property::CssPropertyType::JustifyContent,
compact = get_justify_content
);
get_css_property!(
get_text_align,
get_text_align,
StyleTextAlign,
azul_css::props::property::CssPropertyType::TextAlign,
compact = get_text_align
);
get_css_property!(
get_float,
get_float,
LayoutFloat,
azul_css::props::property::CssPropertyType::Float,
compact = get_float
);
get_css_property!(
get_clear,
get_clear,
LayoutClear,
azul_css::props::property::CssPropertyType::Clear,
compact = get_clear
);
get_css_property!(
get_overflow_x,
get_overflow_x,
LayoutOverflow,
azul_css::props::property::CssPropertyType::OverflowX,
compact = get_overflow_x
);
get_css_property!(
get_overflow_y,
get_overflow_y,
LayoutOverflow,
azul_css::props::property::CssPropertyType::OverflowY,
compact = get_overflow_y
);
get_css_property!(
get_overflow_block,
get_overflow_block,
LayoutOverflow,
azul_css::props::property::CssPropertyType::OverflowBlock
);
get_css_property!(
get_overflow_inline,
get_overflow_inline,
LayoutOverflow,
azul_css::props::property::CssPropertyType::OverflowInline
);
get_css_property!(
get_position,
get_position,
LayoutPosition,
azul_css::props::property::CssPropertyType::Position,
compact = get_position
);
get_css_property!(
get_css_box_sizing,
get_box_sizing,
LayoutBoxSizing,
azul_css::props::property::CssPropertyType::BoxSizing,
compact = get_box_sizing
);
get_css_property!(
get_flex_direction,
get_flex_direction,
LayoutFlexDirection,
azul_css::props::property::CssPropertyType::FlexDirection,
compact = get_flex_direction
);
get_css_property!(
get_align_items,
get_align_items,
LayoutAlignItems,
azul_css::props::property::CssPropertyType::AlignItems,
compact = get_align_items
);
get_css_property!(
get_align_content,
get_align_content,
LayoutAlignContent,
azul_css::props::property::CssPropertyType::AlignContent,
compact = get_align_content
);
get_css_property!(
get_font_weight_property,
get_font_weight,
StyleFontWeight,
azul_css::props::property::CssPropertyType::FontWeight,
compact = get_font_weight
);
get_css_property!(
get_font_style_property,
get_font_style,
StyleFontStyle,
azul_css::props::property::CssPropertyType::FontStyle,
compact = get_font_style
);
get_css_property!(
get_visibility,
get_visibility,
StyleVisibility,
azul_css::props::property::CssPropertyType::Visibility,
compact = get_visibility
);
get_css_property!(
get_white_space_property,
get_white_space,
StyleWhiteSpace,
azul_css::props::property::CssPropertyType::WhiteSpace,
compact = get_white_space
);
get_css_property!(
get_direction_property,
get_direction,
StyleDirection,
azul_css::props::property::CssPropertyType::Direction,
compact = get_direction
);
get_css_property!(
get_unicode_bidi_property,
get_unicode_bidi,
StyleUnicodeBidi,
azul_css::props::property::CssPropertyType::UnicodeBidi
);
get_css_property!(
get_text_box_trim_property,
get_text_box_trim,
StyleTextBoxTrim,
azul_css::props::property::CssPropertyType::TextBoxTrim
);
get_css_property!(
get_text_box_edge_property,
get_text_box_edge,
StyleTextBoxEdge,
azul_css::props::property::CssPropertyType::TextBoxEdge
);
get_css_property!(
get_dominant_baseline_property,
get_dominant_baseline,
StyleDominantBaseline,
azul_css::props::property::CssPropertyType::DominantBaseline
);
get_css_property!(
get_alignment_baseline_property,
get_alignment_baseline,
StyleAlignmentBaseline,
azul_css::props::property::CssPropertyType::AlignmentBaseline
);
get_css_property!(
get_initial_letter_align_property,
get_initial_letter_align,
StyleInitialLetterAlign,
azul_css::props::property::CssPropertyType::InitialLetterAlign
);
get_css_property!(
get_initial_letter_wrap_property,
get_initial_letter_wrap,
StyleInitialLetterWrap,
azul_css::props::property::CssPropertyType::InitialLetterWrap
);
pub fn get_scrollbar_gutter_property(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> MultiValue<StyleScrollbarGutter> {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
let bits = cc.get_scrollbar_gutter_bits(node_id.index());
let val = match bits {
azul_css::compact_cache::SCROLLBAR_GUTTER_AUTO => StyleScrollbarGutter::Auto,
azul_css::compact_cache::SCROLLBAR_GUTTER_STABLE => StyleScrollbarGutter::Stable,
azul_css::compact_cache::SCROLLBAR_GUTTER_BOTH_EDGES => StyleScrollbarGutter::StableBothEdges,
_ => StyleScrollbarGutter::Auto,
};
return MultiValue::Exact(val);
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
let author_css = styled_dom
.css_property_cache
.ptr
.get_scrollbar_gutter(node_data, &node_id, node_state);
if let Some(val) = author_css.and_then(|v| v.get_property().cloned()) {
return MultiValue::Exact(val);
}
MultiValue::Auto
}
get_css_property!(
get_overflow_clip_margin_property,
get_overflow_clip_margin,
StyleOverflowClipMargin,
azul_css::props::property::CssPropertyType::OverflowClipMargin
);
get_css_property!(
get_object_fit_property,
get_object_fit,
StyleObjectFit,
azul_css::props::property::CssPropertyType::ObjectFit
);
pub fn get_text_orientation_property(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> MultiValue<StyleTextOrientation> {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
if !cc.has_text_orientation(node_id.index()) {
return MultiValue::Auto;
}
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
if let Some(val) = styled_dom
.css_property_cache
.ptr
.get_text_orientation(node_data, &node_id, node_state)
.and_then(|v| v.get_property().cloned())
{
return MultiValue::Exact(val);
}
let ua = azul_core::ua_css::get_ua_property(
&node_data.node_type,
azul_css::props::property::CssPropertyType::TextOrientation,
);
if let Some(ua_prop) = ua {
if let Some(val) = extract_property_value::<StyleTextOrientation>(ua_prop) {
return MultiValue::Exact(val);
}
}
MultiValue::Auto
}
get_css_property!(
get_object_position_property,
get_object_position,
StyleObjectPosition,
azul_css::props::property::CssPropertyType::ObjectPosition
);
get_css_property!(
get_aspect_ratio_property,
get_aspect_ratio,
StyleAspectRatio,
azul_css::props::property::CssPropertyType::AspectRatio
);
pub fn get_vertical_align_property(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> MultiValue<StyleVerticalAlign> {
let node_data = &styled_dom.node_data.as_container()[node_id];
let author_css = styled_dom
.css_property_cache
.ptr
.get_vertical_align(node_data, &node_id, node_state);
if let Some(val) = author_css.and_then(|v| v.get_property().cloned()) {
return MultiValue::Exact(val);
}
let ua_css = azul_core::ua_css::get_ua_property(
&node_data.node_type,
azul_css::props::property::CssPropertyType::VerticalAlign,
);
if let Some(ua_prop) = ua_css {
if let Some(val) = extract_property_value::<StyleVerticalAlign>(ua_prop) {
return MultiValue::Exact(val);
}
}
MultiValue::Auto
}
pub fn get_style_border_radius(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> azul_css::props::style::border_radius::StyleBorderRadius {
use azul_css::props::basic::pixel::PixelValue;
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
let idx = node_id.index();
let decode = |raw: i16| -> PixelValue {
if raw >= azul_css::compact_cache::I16_SENTINEL_THRESHOLD {
PixelValue::px(0.0)
} else {
PixelValue::px(raw as f32 / 10.0)
}
};
return StyleBorderRadius {
top_left: decode(cc.get_border_top_left_radius_raw(idx)),
top_right: decode(cc.get_border_top_right_radius_raw(idx)),
bottom_right: decode(cc.get_border_bottom_right_radius_raw(idx)),
bottom_left: decode(cc.get_border_bottom_left_radius_raw(idx)),
};
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
let top_left = styled_dom
.css_property_cache
.ptr
.get_border_top_left_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property_or_default())
.map(|v| v.inner)
.unwrap_or_default();
let top_right = styled_dom
.css_property_cache
.ptr
.get_border_top_right_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property_or_default())
.map(|v| v.inner)
.unwrap_or_default();
let bottom_right = styled_dom
.css_property_cache
.ptr
.get_border_bottom_right_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property_or_default())
.map(|v| v.inner)
.unwrap_or_default();
let bottom_left = styled_dom
.css_property_cache
.ptr
.get_border_bottom_left_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property_or_default())
.map(|v| v.inner)
.unwrap_or_default();
StyleBorderRadius {
top_left,
top_right,
bottom_right,
bottom_left,
}
}
pub fn get_border_radius(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
element_size: PhysicalSizeImport,
viewport_size: LogicalSize,
) -> BorderRadius {
use azul_css::props::basic::{PhysicalSize, PropertyContext, ResolutionContext};
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
let idx = node_id.index();
let tl = cc.get_border_top_left_radius_raw(idx);
let tr = cc.get_border_top_right_radius_raw(idx);
let br = cc.get_border_bottom_right_radius_raw(idx);
let bl = cc.get_border_bottom_left_radius_raw(idx);
let thresh = azul_css::compact_cache::I16_SENTINEL_THRESHOLD;
let decode = |raw: i16| -> f32 {
if raw >= thresh { 0.0 } else { raw as f32 / 10.0 }
};
return BorderRadius {
top_left: decode(tl),
top_right: decode(tr),
bottom_right: decode(br),
bottom_left: decode(bl),
};
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
let element_font_size = get_element_font_size(styled_dom, node_id, node_state);
let parent_font_size = styled_dom
.node_hierarchy
.as_container()
.get(node_id)
.and_then(|node| node.parent_id())
.map(|p| get_element_font_size(styled_dom, p, node_state))
.unwrap_or(azul_css::props::basic::pixel::DEFAULT_FONT_SIZE);
let root_font_size = get_root_font_size(styled_dom, node_state);
let context = ResolutionContext {
element_font_size,
parent_font_size,
root_font_size,
containing_block_size: PhysicalSize::new(0.0, 0.0), element_size: Some(PhysicalSize::new(element_size.width, element_size.height)),
viewport_size: PhysicalSize::new(viewport_size.width, viewport_size.height),
};
let top_left = styled_dom
.css_property_cache
.ptr
.get_border_top_left_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property().cloned())
.unwrap_or_default();
let top_right = styled_dom
.css_property_cache
.ptr
.get_border_top_right_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property().cloned())
.unwrap_or_default();
let bottom_right = styled_dom
.css_property_cache
.ptr
.get_border_bottom_right_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property().cloned())
.unwrap_or_default();
let bottom_left = styled_dom
.css_property_cache
.ptr
.get_border_bottom_left_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property().cloned())
.unwrap_or_default();
BorderRadius {
top_left: top_left
.inner
.resolve_with_context(&context, PropertyContext::BorderRadius),
top_right: top_right
.inner
.resolve_with_context(&context, PropertyContext::BorderRadius),
bottom_right: bottom_right
.inner
.resolve_with_context(&context, PropertyContext::BorderRadius),
bottom_left: bottom_left
.inner
.resolve_with_context(&context, PropertyContext::BorderRadius),
}
}
pub fn get_z_index(styled_dom: &StyledDom, node_id: Option<NodeId>) -> i32 {
use azul_css::props::layout::position::LayoutZIndex;
let node_id = match node_id {
Some(id) => id,
None => return 0,
};
let node_state = &styled_dom.styled_nodes.as_container()[node_id].styled_node_state;
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
let raw = cc.get_z_index(node_id.index());
if raw == azul_css::compact_cache::I16_AUTO {
return 0;
}
if raw < azul_css::compact_cache::I16_SENTINEL_THRESHOLD {
return raw as i32;
}
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom
.css_property_cache
.ptr
.get_z_index(node_data, &node_id, &node_state)
.and_then(|v| v.get_property())
.map(|z| match z {
LayoutZIndex::Auto => 0,
LayoutZIndex::Integer(i) => *i,
})
.unwrap_or(0)
}
pub fn is_z_index_auto(styled_dom: &StyledDom, node_id: Option<NodeId>) -> bool {
use azul_css::props::layout::position::LayoutZIndex;
let node_id = match node_id {
Some(id) => id,
None => return true,
};
let node_state = &styled_dom.styled_nodes.as_container()[node_id].styled_node_state;
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
let raw = cc.get_z_index(node_id.index());
if raw == azul_css::compact_cache::I16_AUTO {
return true;
}
if raw < azul_css::compact_cache::I16_SENTINEL_THRESHOLD {
return false; }
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom
.css_property_cache
.ptr
.get_z_index(node_data, &node_id, &node_state)
.and_then(|v| v.get_property())
.map(|z| matches!(z, LayoutZIndex::Auto))
.unwrap_or(true) }
pub fn get_background_color(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> ColorU {
let node_data = &styled_dom.node_data.as_container()[node_id];
let cache = &styled_dom.css_property_cache.ptr;
let get_node_bg = |nid: NodeId, ndata: &azul_core::dom::NodeData, state: &StyledNodeState| {
if state.is_normal() {
if let Some(ref cc) = cache.compact_cache {
if !cc.has_background(nid.index()) {
return None;
}
}
}
cache
.get_background_content(ndata, &nid, state)
.and_then(|bg| bg.get_property())
.and_then(|bg_vec| bg_vec.get(0).cloned())
.and_then(|first_bg| match &first_bg {
azul_css::props::style::StyleBackgroundContent::Color(color) => Some(color.clone()),
azul_css::props::style::StyleBackgroundContent::Image(_) => None, _ => None,
})
};
let own_bg = get_node_bg(node_id, node_data, node_state);
if !matches!(node_data.node_type, NodeType::Html) || own_bg.is_some() {
return own_bg.unwrap_or(ColorU {
r: 0,
g: 0,
b: 0,
a: 0,
});
}
let first_child = styled_dom
.node_hierarchy
.as_container()
.get(node_id)
.and_then(|node| node.first_child_id(node_id));
let Some(first_child) = first_child else {
return ColorU {
r: 0,
g: 0,
b: 0,
a: 0,
};
};
let first_child_data = &styled_dom.node_data.as_container()[first_child];
if !matches!(first_child_data.node_type, NodeType::Body) {
return ColorU {
r: 0,
g: 0,
b: 0,
a: 0,
};
}
let first_child_state = &styled_dom.styled_nodes.as_container()[first_child].styled_node_state;
get_node_bg(first_child, first_child_data, first_child_state).unwrap_or(ColorU {
r: 0,
g: 0,
b: 0,
a: 0,
})
}
pub fn get_background_contents(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Vec<azul_css::props::style::StyleBackgroundContent> {
use azul_core::dom::NodeType;
use azul_css::props::style::StyleBackgroundContent;
let node_data = &styled_dom.node_data.as_container()[node_id];
let cache = &styled_dom.css_property_cache.ptr;
let get_node_backgrounds =
|nid: NodeId, ndata: &azul_core::dom::NodeData, state: &StyledNodeState|
-> Vec<StyleBackgroundContent> {
if state.is_normal() {
if let Some(ref cc) = cache.compact_cache {
if !cc.has_background(nid.index()) {
return Vec::new();
}
}
}
cache
.get_background_content(ndata, &nid, state)
.and_then(|bg| bg.get_property())
.map(|bg_vec| bg_vec.iter().cloned().collect())
.unwrap_or_default()
};
let own_backgrounds = get_node_backgrounds(node_id, node_data, node_state);
if !matches!(node_data.node_type, NodeType::Html) || !own_backgrounds.is_empty() {
return own_backgrounds;
}
let first_child = styled_dom
.node_hierarchy
.as_container()
.get(node_id)
.and_then(|node| node.first_child_id(node_id));
let Some(first_child) = first_child else {
return own_backgrounds;
};
let first_child_data = &styled_dom.node_data.as_container()[first_child];
if !matches!(first_child_data.node_type, NodeType::Body) {
return own_backgrounds;
}
let first_child_state = &styled_dom.styled_nodes.as_container()[first_child].styled_node_state;
get_node_backgrounds(first_child, first_child_data, first_child_state)
}
pub struct BorderInfo {
pub widths: crate::solver3::display_list::StyleBorderWidths,
pub colors: crate::solver3::display_list::StyleBorderColors,
pub styles: crate::solver3::display_list::StyleBorderStyles,
}
pub fn get_border_info(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> BorderInfo {
use crate::solver3::display_list::{StyleBorderColors, StyleBorderStyles, StyleBorderWidths};
use azul_css::css::CssPropertyValue;
use azul_css::props::basic::color::ColorU;
use azul_css::props::basic::pixel::PixelValue;
use azul_css::props::style::{
LayoutBorderTopWidth, LayoutBorderRightWidth,
LayoutBorderBottomWidth, LayoutBorderLeftWidth,
};
use azul_css::props::style::border::{
BorderStyle, StyleBorderTopColor, StyleBorderRightColor,
StyleBorderBottomColor, StyleBorderLeftColor,
StyleBorderTopStyle, StyleBorderRightStyle,
StyleBorderBottomStyle, StyleBorderLeftStyle,
};
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
let idx = node_id.index();
let make_width_px = |raw: i16| -> Option<PixelValue> {
if raw == azul_css::compact_cache::I16_AUTO
|| raw == azul_css::compact_cache::I16_INITIAL
|| raw >= azul_css::compact_cache::I16_SENTINEL_THRESHOLD
{
None
} else {
Some(PixelValue::px(raw as f32 / 10.0))
}
};
let widths = StyleBorderWidths {
top: make_width_px(cc.get_border_top_width_raw(idx))
.map(|px| CssPropertyValue::Exact(LayoutBorderTopWidth { inner: px })),
right: make_width_px(cc.get_border_right_width_raw(idx))
.map(|px| CssPropertyValue::Exact(LayoutBorderRightWidth { inner: px })),
bottom: make_width_px(cc.get_border_bottom_width_raw(idx))
.map(|px| CssPropertyValue::Exact(LayoutBorderBottomWidth { inner: px })),
left: make_width_px(cc.get_border_left_width_raw(idx))
.map(|px| CssPropertyValue::Exact(LayoutBorderLeftWidth { inner: px })),
};
let make_color = |raw: u32| -> Option<ColorU> {
if raw == 0 { None } else {
Some(ColorU {
r: ((raw >> 24) & 0xFF) as u8,
g: ((raw >> 16) & 0xFF) as u8,
b: ((raw >> 8) & 0xFF) as u8,
a: (raw & 0xFF) as u8,
})
}
};
let colors = StyleBorderColors {
top: make_color(cc.get_border_top_color_raw(idx))
.map(|c| CssPropertyValue::Exact(StyleBorderTopColor { inner: c })),
right: make_color(cc.get_border_right_color_raw(idx))
.map(|c| CssPropertyValue::Exact(StyleBorderRightColor { inner: c })),
bottom: make_color(cc.get_border_bottom_color_raw(idx))
.map(|c| CssPropertyValue::Exact(StyleBorderBottomColor { inner: c })),
left: make_color(cc.get_border_left_color_raw(idx))
.map(|c| CssPropertyValue::Exact(StyleBorderLeftColor { inner: c })),
};
let styles = StyleBorderStyles {
top: Some(CssPropertyValue::Exact(StyleBorderTopStyle {
inner: cc.get_border_top_style(idx),
})),
right: Some(CssPropertyValue::Exact(StyleBorderRightStyle {
inner: cc.get_border_right_style(idx),
})),
bottom: Some(CssPropertyValue::Exact(StyleBorderBottomStyle {
inner: cc.get_border_bottom_style(idx),
})),
left: Some(CssPropertyValue::Exact(StyleBorderLeftStyle {
inner: cc.get_border_left_style(idx),
})),
};
return BorderInfo { widths, colors, styles };
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
let widths = StyleBorderWidths {
top: styled_dom
.css_property_cache
.ptr
.get_border_top_width(node_data, &node_id, node_state)
.cloned(),
right: styled_dom
.css_property_cache
.ptr
.get_border_right_width(node_data, &node_id, node_state)
.cloned(),
bottom: styled_dom
.css_property_cache
.ptr
.get_border_bottom_width(node_data, &node_id, node_state)
.cloned(),
left: styled_dom
.css_property_cache
.ptr
.get_border_left_width(node_data, &node_id, node_state)
.cloned(),
};
let colors = StyleBorderColors {
top: styled_dom
.css_property_cache
.ptr
.get_border_top_color(node_data, &node_id, node_state)
.cloned(),
right: styled_dom
.css_property_cache
.ptr
.get_border_right_color(node_data, &node_id, node_state)
.cloned(),
bottom: styled_dom
.css_property_cache
.ptr
.get_border_bottom_color(node_data, &node_id, node_state)
.cloned(),
left: styled_dom
.css_property_cache
.ptr
.get_border_left_color(node_data, &node_id, node_state)
.cloned(),
};
let styles = StyleBorderStyles {
top: styled_dom
.css_property_cache
.ptr
.get_border_top_style(node_data, &node_id, node_state)
.cloned(),
right: styled_dom
.css_property_cache
.ptr
.get_border_right_style(node_data, &node_id, node_state)
.cloned(),
bottom: styled_dom
.css_property_cache
.ptr
.get_border_bottom_style(node_data, &node_id, node_state)
.cloned(),
left: styled_dom
.css_property_cache
.ptr
.get_border_left_style(node_data, &node_id, node_state)
.cloned(),
};
BorderInfo {
widths,
colors,
styles,
}
}
pub fn get_inline_border_info(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
border_info: &BorderInfo,
) -> Option<crate::text3::cache::InlineBorderInfo> {
use crate::text3::cache::InlineBorderInfo;
fn get_border_width_px(
width: &Option<
azul_css::css::CssPropertyValue<azul_css::props::style::border::LayoutBorderTopWidth>,
>,
) -> f32 {
width
.as_ref()
.and_then(|v| v.get_property())
.map(|w| w.inner.number.get())
.unwrap_or(0.0)
}
fn get_border_width_px_right(
width: &Option<
azul_css::css::CssPropertyValue<azul_css::props::style::border::LayoutBorderRightWidth>,
>,
) -> f32 {
width
.as_ref()
.and_then(|v| v.get_property())
.map(|w| w.inner.number.get())
.unwrap_or(0.0)
}
fn get_border_width_px_bottom(
width: &Option<
azul_css::css::CssPropertyValue<
azul_css::props::style::border::LayoutBorderBottomWidth,
>,
>,
) -> f32 {
width
.as_ref()
.and_then(|v| v.get_property())
.map(|w| w.inner.number.get())
.unwrap_or(0.0)
}
fn get_border_width_px_left(
width: &Option<
azul_css::css::CssPropertyValue<azul_css::props::style::border::LayoutBorderLeftWidth>,
>,
) -> f32 {
width
.as_ref()
.and_then(|v| v.get_property())
.map(|w| w.inner.number.get())
.unwrap_or(0.0)
}
fn get_border_color_top(
color: &Option<
azul_css::css::CssPropertyValue<azul_css::props::style::border::StyleBorderTopColor>,
>,
) -> ColorU {
color
.as_ref()
.and_then(|v| v.get_property())
.map(|c| c.inner)
.unwrap_or(ColorU::BLACK)
}
fn get_border_color_right(
color: &Option<
azul_css::css::CssPropertyValue<azul_css::props::style::border::StyleBorderRightColor>,
>,
) -> ColorU {
color
.as_ref()
.and_then(|v| v.get_property())
.map(|c| c.inner)
.unwrap_or(ColorU::BLACK)
}
fn get_border_color_bottom(
color: &Option<
azul_css::css::CssPropertyValue<azul_css::props::style::border::StyleBorderBottomColor>,
>,
) -> ColorU {
color
.as_ref()
.and_then(|v| v.get_property())
.map(|c| c.inner)
.unwrap_or(ColorU::BLACK)
}
fn get_border_color_left(
color: &Option<
azul_css::css::CssPropertyValue<azul_css::props::style::border::StyleBorderLeftColor>,
>,
) -> ColorU {
color
.as_ref()
.and_then(|v| v.get_property())
.map(|c| c.inner)
.unwrap_or(ColorU::BLACK)
}
fn get_border_radius_px(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<f32> {
let node_data = &styled_dom.node_data.as_container()[node_id];
let top_left = styled_dom
.css_property_cache
.ptr
.get_border_top_left_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property().cloned())
.map(|v| v.inner.number.get());
let top_right = styled_dom
.css_property_cache
.ptr
.get_border_top_right_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property().cloned())
.map(|v| v.inner.number.get());
let bottom_left = styled_dom
.css_property_cache
.ptr
.get_border_bottom_left_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property().cloned())
.map(|v| v.inner.number.get());
let bottom_right = styled_dom
.css_property_cache
.ptr
.get_border_bottom_right_radius(node_data, &node_id, node_state)
.and_then(|br| br.get_property().cloned())
.map(|v| v.inner.number.get());
let radii: Vec<f32> = [top_left, top_right, bottom_left, bottom_right]
.into_iter()
.filter_map(|r| r)
.collect();
if radii.is_empty() {
None
} else {
Some(radii.into_iter().fold(0.0f32, |a, b| a.max(b)))
}
}
let top = get_border_width_px(&border_info.widths.top);
let right = get_border_width_px_right(&border_info.widths.right);
let bottom = get_border_width_px_bottom(&border_info.widths.bottom);
let left = get_border_width_px_left(&border_info.widths.left);
fn resolve_padding(mv: MultiValue<PixelValue>) -> f32 {
match mv {
MultiValue::Exact(pv) => {
super::calc::resolve_pixel_value(&pv, 0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE)
}
_ => 0.0,
}
}
let p_top = resolve_padding(get_css_padding_top(styled_dom, node_id, node_state));
let p_right = resolve_padding(get_css_padding_right(styled_dom, node_id, node_state));
let p_bottom = resolve_padding(get_css_padding_bottom(styled_dom, node_id, node_state));
let p_left = resolve_padding(get_css_padding_left(styled_dom, node_id, node_state));
let has_border = top > 0.0 || right > 0.0 || bottom > 0.0 || left > 0.0;
let has_padding = p_top > 0.0 || p_right > 0.0 || p_bottom > 0.0 || p_left > 0.0;
if !has_border && !has_padding {
return None;
}
let is_rtl = matches!(
get_direction_property(styled_dom, node_id, node_state),
MultiValue::Exact(StyleDirection::Rtl)
);
Some(InlineBorderInfo {
top,
right,
bottom,
left,
top_color: get_border_color_top(&border_info.colors.top),
right_color: get_border_color_right(&border_info.colors.right),
bottom_color: get_border_color_bottom(&border_info.colors.bottom),
left_color: get_border_color_left(&border_info.colors.left),
radius: get_border_radius_px(styled_dom, node_id, node_state),
padding_top: p_top,
padding_right: p_right,
padding_bottom: p_bottom,
padding_left: p_left,
is_first_fragment: true,
is_last_fragment: true,
is_rtl,
})
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SelectionStyle {
pub bg_color: ColorU,
pub text_color: Option<ColorU>,
pub radius: f32,
}
pub fn get_selection_style(
styled_dom: &StyledDom,
node_id: Option<NodeId>,
system_style: Option<&std::sync::Arc<azul_css::system::SystemStyle>>,
) -> SelectionStyle {
let Some(node_id) = node_id else {
return SelectionStyle::default();
};
let node_data = &styled_dom.node_data.as_container()[node_id];
let node_state = &StyledNodeState::default();
let default_bg = system_style
.and_then(|ss| ss.colors.selection_background.as_option().copied())
.unwrap_or(ColorU {
r: 51,
g: 153,
b: 255, a: 128, });
let bg_color = styled_dom
.css_property_cache
.ptr
.get_selection_background_color(node_data, &node_id, node_state)
.and_then(|c| c.get_property().cloned())
.map(|c| c.inner)
.unwrap_or(default_bg);
let default_text = system_style
.and_then(|ss| ss.colors.selection_text.as_option().copied());
let text_color = styled_dom
.css_property_cache
.ptr
.get_selection_color(node_data, &node_id, node_state)
.and_then(|c| c.get_property().cloned())
.map(|c| c.inner)
.or(default_text);
let radius = styled_dom
.css_property_cache
.ptr
.get_selection_radius(node_data, &node_id, node_state)
.and_then(|r| r.get_property().cloned())
.map(|r| r.inner.to_pixels_internal(0.0, 16.0, 16.0)) .unwrap_or(0.0);
SelectionStyle {
bg_color,
text_color,
radius,
}
}
#[derive(Debug, Clone, Copy)]
pub struct CaretStyle {
pub color: ColorU,
pub width: f32,
pub animation_duration: u32,
}
impl Default for CaretStyle {
fn default() -> Self {
Self {
color: ColorU::BLACK,
width: 2.0,
animation_duration: 500,
}
}
}
pub fn get_caret_style(styled_dom: &StyledDom, node_id: Option<NodeId>) -> CaretStyle {
let Some(node_id) = node_id else {
return CaretStyle::default();
};
let node_data = &styled_dom.node_data.as_container()[node_id];
let node_state = &StyledNodeState::default();
let color = styled_dom
.css_property_cache
.ptr
.get_caret_color(node_data, &node_id, node_state)
.and_then(|c| c.get_property().cloned())
.map(|c| c.inner)
.unwrap_or(ColorU::BLACK);
let width = styled_dom
.css_property_cache
.ptr
.get_caret_width(node_data, &node_id, node_state)
.and_then(|w| w.get_property().cloned())
.map(|w| w.inner.to_pixels_internal(0.0, 16.0, 16.0)) .unwrap_or(2.0);
let animation_duration = styled_dom
.css_property_cache
.ptr
.get_caret_animation_duration(node_data, &node_id, node_state)
.and_then(|d| d.get_property().cloned())
.map(|d| d.inner.inner) .unwrap_or(500);
CaretStyle {
color,
width,
animation_duration,
}
}
pub fn get_scrollbar_info_from_layout(node: &LayoutNode) -> ScrollbarRequirements {
node.scrollbar_info
.clone()
.unwrap_or_default()
}
pub fn get_layout_scrollbar_width_px<T: crate::font_traits::ParsedFontTrait>(
ctx: &crate::solver3::LayoutContext<'_, T>,
dom_id: NodeId,
styled_node_state: &StyledNodeState,
) -> f32 {
let style = get_scrollbar_style(
ctx.styled_dom,
dom_id,
styled_node_state,
ctx.system_style.as_deref(),
);
style.reserve_width_px
}
get_css_property!(
get_display_property_internal,
get_display,
LayoutDisplay,
azul_css::props::property::CssPropertyType::Display,
compact = get_display
);
pub fn get_display_property(
styled_dom: &StyledDom,
dom_id: Option<NodeId>,
) -> MultiValue<LayoutDisplay> {
let Some(id) = dom_id else {
return MultiValue::Exact(LayoutDisplay::Inline);
};
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
get_display_property_internal(styled_dom, id, node_state)
}
pub fn blockify_display(raw_display: LayoutDisplay) -> LayoutDisplay {
match raw_display {
LayoutDisplay::Inline => LayoutDisplay::Block,
LayoutDisplay::InlineBlock => LayoutDisplay::Block,
LayoutDisplay::InlineFlex => LayoutDisplay::Flex,
LayoutDisplay::InlineTable => LayoutDisplay::Table,
LayoutDisplay::InlineGrid => LayoutDisplay::Grid,
LayoutDisplay::TableRowGroup
| LayoutDisplay::TableColumn
| LayoutDisplay::TableColumnGroup
| LayoutDisplay::TableHeaderGroup
| LayoutDisplay::TableFooterGroup
| LayoutDisplay::TableRow
| LayoutDisplay::TableCell
| LayoutDisplay::TableCaption => LayoutDisplay::Block,
other => other,
}
}
pub fn get_computed_display(
raw_display: LayoutDisplay,
is_absolute_or_fixed: bool,
is_floated: bool,
is_root: bool,
is_flex_grid_child: bool,
) -> LayoutDisplay {
if raw_display == LayoutDisplay::None {
return LayoutDisplay::None;
}
if is_absolute_or_fixed || is_floated || is_root || is_flex_grid_child {
blockify_display(raw_display)
} else {
raw_display
}
}
pub fn get_vertical_align_for_node(
styled_dom: &StyledDom,
dom_id: NodeId,
) -> crate::text3::cache::VerticalAlign {
let node_state = &styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
let va = match get_vertical_align_property(styled_dom, dom_id, node_state) {
MultiValue::Exact(v) => v,
_ => StyleVerticalAlign::default(),
};
match va {
StyleVerticalAlign::Baseline => crate::text3::cache::VerticalAlign::Baseline,
StyleVerticalAlign::Top => crate::text3::cache::VerticalAlign::Top,
StyleVerticalAlign::Middle => crate::text3::cache::VerticalAlign::Middle,
StyleVerticalAlign::Bottom => crate::text3::cache::VerticalAlign::Bottom,
StyleVerticalAlign::Sub => crate::text3::cache::VerticalAlign::Sub,
StyleVerticalAlign::Superscript => crate::text3::cache::VerticalAlign::Super,
StyleVerticalAlign::TextTop => crate::text3::cache::VerticalAlign::TextTop,
StyleVerticalAlign::TextBottom => crate::text3::cache::VerticalAlign::TextBottom,
StyleVerticalAlign::Percentage(p) => {
let font_size = get_element_font_size(styled_dom, dom_id, node_state);
let line_height = get_line_height_value(styled_dom, dom_id, node_state)
.map(|lh| lh.inner.normalized() * font_size)
.unwrap_or(font_size * 1.2);
crate::text3::cache::VerticalAlign::Offset(p.normalized() * line_height)
}
StyleVerticalAlign::Length(l) => {
let font_size = get_element_font_size(styled_dom, dom_id, node_state);
let px = super::calc::resolve_pixel_value(&l, 0.0, font_size, font_size);
crate::text3::cache::VerticalAlign::Offset(px)
}
}
}
pub fn get_style_properties(
styled_dom: &StyledDom,
dom_id: NodeId,
system_style: Option<&std::sync::Arc<azul_css::system::SystemStyle>>,
viewport_size: azul_css::props::basic::PhysicalSize,
) -> StyleProperties {
use azul_css::props::basic::{PhysicalSize, PropertyContext, ResolutionContext};
let node_data = &styled_dom.node_data.as_container()[dom_id];
let node_state = &styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
let cache = &styled_dom.css_property_cache.ptr;
use azul_css::props::basic::font::{StyleFontFamily, StyleFontFamilyVec};
let font_families = if node_state.is_normal() {
cache.compact_cache.as_ref()
.and_then(|cc| {
let fh = cc.tier2b_text[dom_id.index()].font_family_hash;
if fh == 0 { return None; }
cc.font_hash_to_families.get(&fh).cloned()
})
.unwrap_or_else(|| {
StyleFontFamilyVec::from_vec(vec![StyleFontFamily::System("serif".into())])
})
} else {
cache
.get_font_family(node_data, &dom_id, node_state)
.and_then(|v| v.get_property().cloned())
.unwrap_or_else(|| {
StyleFontFamilyVec::from_vec(vec![StyleFontFamily::System("serif".into())])
})
};
let parent_font_size = get_parent_font_size(styled_dom, dom_id, node_state);
let root_font_size = get_root_font_size(styled_dom, node_state);
let font_size_context = ResolutionContext {
element_font_size: azul_css::props::basic::pixel::DEFAULT_FONT_SIZE,
parent_font_size,
root_font_size,
containing_block_size: PhysicalSize::new(0.0, 0.0),
element_size: None,
viewport_size,
};
let font_size = {
let mut fast_font_size: Option<f32> = None;
let mut compact_said_inherit = false;
if node_state.is_normal() {
if let Some(ref cc) = cache.compact_cache {
let raw = cc.get_font_size_raw(dom_id.index());
if raw == azul_css::compact_cache::U32_SENTINEL
|| raw == azul_css::compact_cache::U32_INHERIT
|| raw == azul_css::compact_cache::U32_INITIAL
{
compact_said_inherit = true;
} else if let Some(pv) = azul_css::compact_cache::decode_pixel_value_u32(raw) {
fast_font_size = Some(pv.resolve_with_context(
&font_size_context,
PropertyContext::FontSize,
));
}
}
}
if let Some(fs) = fast_font_size {
fs
} else if compact_said_inherit {
parent_font_size
} else {
cache
.get_font_size(node_data, &dom_id, node_state)
.and_then(|v| v.get_property().cloned())
.map(|v| {
v.inner
.resolve_with_context(&font_size_context, PropertyContext::FontSize)
})
.unwrap_or(parent_font_size)
}
};
let color_from_cache = {
let mut fast_color = None;
if node_state.is_normal() {
if let Some(ref cc) = cache.compact_cache {
let raw = cc.get_text_color_raw(dom_id.index());
if raw != 0 {
fast_color = Some(ColorU {
r: (raw >> 24) as u8,
g: (raw >> 16) as u8,
b: (raw >> 8) as u8,
a: raw as u8,
});
}
}
}
fast_color.or_else(|| {
cache
.get_text_color(node_data, &dom_id, node_state)
.and_then(|v| v.get_property().cloned())
.map(|v| v.inner)
})
};
let color = color_from_cache.unwrap_or(ColorU::BLACK);
let line_height = {
let mut fast_lh = None;
let mut sentinel_normal = false;
if node_state.is_normal() {
if let Some(ref cc) = cache.compact_cache {
if let Some(normalized) = cc.get_line_height(dom_id.index()) {
fast_lh = Some(crate::text3::cache::LineHeight::Px(normalized / 100.0 * font_size));
} else {
sentinel_normal = true;
}
}
}
if sentinel_normal {
crate::text3::cache::LineHeight::Normal
} else {
fast_lh.unwrap_or_else(|| {
cache
.get_line_height(node_data, &dom_id, node_state)
.and_then(|v| v.get_property().cloned())
.map(|v| crate::text3::cache::LineHeight::Px(v.inner.normalized() * font_size))
.unwrap_or(crate::text3::cache::LineHeight::Normal)
})
}
};
use azul_css::props::layout::LayoutDisplay;
let display = match get_display_property(styled_dom, Some(dom_id)) {
MultiValue::Exact(v) => v,
_ => LayoutDisplay::Inline,
};
let (background_color, background_content, border) =
if matches!(display, LayoutDisplay::Inline | LayoutDisplay::InlineBlock) {
let bg = get_background_color(styled_dom, dom_id, node_state);
let bg_color = if bg.a > 0 { Some(bg) } else { None };
let bg_contents = get_background_contents(styled_dom, dom_id, node_state);
let border_info = get_border_info(styled_dom, dom_id, node_state);
let inline_border =
get_inline_border_info(styled_dom, dom_id, node_state, &border_info);
(bg_color, bg_contents, inline_border)
} else {
(None, Vec::new(), None)
};
let font_weight = match get_font_weight_property(styled_dom, dom_id, node_state) {
MultiValue::Exact(v) => v,
_ => StyleFontWeight::Normal,
};
let font_style = match get_font_style_property(styled_dom, dom_id, node_state) {
MultiValue::Exact(v) => v,
_ => StyleFontStyle::Normal,
};
let fc_weight = super::fc::convert_font_weight(font_weight);
let fc_style = super::fc::convert_font_style(font_style);
let font_stack = {
let font_ref = (0..font_families.len())
.find_map(|i| {
match font_families.get(i).unwrap() {
azul_css::props::basic::font::StyleFontFamily::Ref(r) => Some(r.clone()),
_ => None,
}
});
let platform = system_style.map(|ss| &ss.platform);
if let Some(font_ref) = font_ref {
FontStack::Ref(font_ref)
} else {
let mut stack = Vec::with_capacity(font_families.len() + 3);
for i in 0..font_families.len() {
let family = font_families.get(i).unwrap();
if let azul_css::props::basic::font::StyleFontFamily::SystemType(system_type) = family {
if let Some(platform) = platform {
let font_names = system_type.get_fallback_chain(platform);
let system_weight = if system_type.is_bold() {
rust_fontconfig::FcWeight::Bold
} else {
fc_weight
};
let system_style_val = if system_type.is_italic() {
crate::text3::cache::FontStyle::Italic
} else {
fc_style
};
for font_name in font_names {
stack.push(crate::text3::cache::FontSelector {
family: font_name.to_string(),
weight: system_weight,
style: system_style_val,
unicode_ranges: Vec::new(),
});
}
} else {
stack.push(crate::text3::cache::FontSelector {
family: "sans-serif".to_string(),
weight: fc_weight,
style: fc_style,
unicode_ranges: Vec::new(),
});
}
} else {
stack.push(crate::text3::cache::FontSelector {
family: family.as_string(),
weight: fc_weight,
style: fc_style,
unicode_ranges: Vec::new(),
});
}
}
let generic_fallbacks = ["sans-serif", "serif", "monospace"];
for fallback in &generic_fallbacks {
if !stack
.iter()
.any(|f| f.family.to_lowercase() == fallback.to_lowercase())
{
stack.push(crate::text3::cache::FontSelector {
family: fallback.to_string(),
weight: rust_fontconfig::FcWeight::Normal,
style: crate::text3::cache::FontStyle::Normal,
unicode_ranges: Vec::new(),
});
}
}
FontStack::Stack(stack)
}
};
let letter_spacing = {
let mut fast_ls = None;
if node_state.is_normal() {
if let Some(ref cc) = cache.compact_cache {
if let Some(px_val) = cc.get_letter_spacing(dom_id.index()) {
fast_ls = Some(crate::text3::cache::Spacing::Px(px_val.round() as i32));
}
}
}
fast_ls.unwrap_or_else(|| {
cache
.get_letter_spacing(node_data, &dom_id, node_state)
.and_then(|v| v.get_property().cloned())
.map(|v| {
let px_value = v.inner.resolve_with_context(&font_size_context, PropertyContext::FontSize);
crate::text3::cache::Spacing::Px(px_value.round() as i32)
})
.unwrap_or_default()
})
};
let word_spacing = {
let mut fast_ws = None;
if node_state.is_normal() {
if let Some(ref cc) = cache.compact_cache {
if let Some(px_val) = cc.get_word_spacing(dom_id.index()) {
fast_ws = Some(crate::text3::cache::Spacing::Px(px_val.round() as i32));
}
}
}
fast_ws.unwrap_or_else(|| {
cache
.get_word_spacing(node_data, &dom_id, node_state)
.and_then(|v| v.get_property().cloned())
.map(|v| {
let px_value = v.inner.resolve_with_context(&font_size_context, PropertyContext::FontSize);
crate::text3::cache::Spacing::Px(px_value.round() as i32)
})
.unwrap_or_default()
})
};
let text_decoration = {
let mut skip_walk = false;
if node_state.is_normal() {
if let Some(ref cc) = cache.compact_cache {
if !cc.has_text_decoration(dom_id.index()) {
skip_walk = true;
}
}
}
if skip_walk {
crate::text3::cache::TextDecoration::default()
} else {
cache
.get_text_decoration(node_data, &dom_id, node_state)
.and_then(|v| v.get_property().cloned())
.map(|v| crate::text3::cache::TextDecoration::from_css(v))
.unwrap_or_default()
}
};
let tab_size = {
let mut fast_tab = None;
if node_state.is_normal() {
if let Some(ref cc) = cache.compact_cache {
let raw = cc.get_tab_size_raw(dom_id.index());
if raw < azul_css::compact_cache::I16_SENTINEL_THRESHOLD {
fast_tab = Some(raw as f32 / 10.0);
} else {
fast_tab = Some(8.0);
}
}
}
fast_tab.unwrap_or_else(|| {
cache
.get_tab_size(node_data, &dom_id, node_state)
.and_then(|v| v.get_property().cloned())
.map(|v| v.inner.number.get())
.unwrap_or(8.0)
})
};
let properties = StyleProperties {
font_stack,
font_size_px: font_size,
color,
background_color,
background_content,
border,
line_height,
letter_spacing,
word_spacing,
text_decoration,
tab_size,
..Default::default()
};
properties
}
pub fn get_list_style_type(styled_dom: &StyledDom, dom_id: Option<NodeId>) -> StyleListStyleType {
let Some(id) = dom_id else {
return StyleListStyleType::default();
};
let node_data = &styled_dom.node_data.as_container()[id];
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
styled_dom
.css_property_cache
.ptr
.get_list_style_type(node_data, &id, node_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default()
}
pub fn get_list_style_position(
styled_dom: &StyledDom,
dom_id: Option<NodeId>,
) -> StyleListStylePosition {
let Some(id) = dom_id else {
return StyleListStylePosition::default();
};
let node_data = &styled_dom.node_data.as_container()[id];
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
styled_dom
.css_property_cache
.ptr
.get_list_style_position(node_data, &id, node_state)
.and_then(|v| v.get_property().copied())
.unwrap_or_default()
}
use azul_css::props::layout::{
LayoutInsetBottom, LayoutLeft, LayoutMarginBottom, LayoutMarginLeft, LayoutMarginRight,
LayoutMarginTop, LayoutMaxHeight, LayoutMaxWidth, LayoutMinHeight, LayoutMinWidth,
LayoutPaddingBottom, LayoutPaddingLeft, LayoutPaddingRight, LayoutPaddingTop, LayoutRight,
LayoutTop,
};
get_css_property_pixel!(
get_css_left,
get_left,
azul_css::props::property::CssPropertyType::Left,
compact_i16 = get_left
);
get_css_property_pixel!(
get_css_right,
get_right,
azul_css::props::property::CssPropertyType::Right,
compact_i16 = get_right
);
get_css_property_pixel!(
get_css_top,
get_top,
azul_css::props::property::CssPropertyType::Top,
compact_i16 = get_top
);
get_css_property_pixel!(
get_css_bottom,
get_bottom,
azul_css::props::property::CssPropertyType::Bottom,
compact_i16 = get_bottom
);
get_css_property_pixel!(
get_css_margin_left,
get_margin_left,
azul_css::props::property::CssPropertyType::MarginLeft,
compact_i16 = get_margin_left_raw
);
get_css_property_pixel!(
get_css_margin_right,
get_margin_right,
azul_css::props::property::CssPropertyType::MarginRight,
compact_i16 = get_margin_right_raw
);
get_css_property_pixel!(
get_css_margin_top,
get_margin_top,
azul_css::props::property::CssPropertyType::MarginTop,
compact_i16 = get_margin_top_raw
);
get_css_property_pixel!(
get_css_margin_bottom,
get_margin_bottom,
azul_css::props::property::CssPropertyType::MarginBottom,
compact_i16 = get_margin_bottom_raw
);
get_css_property_pixel!(
get_css_padding_left,
get_padding_left,
azul_css::props::property::CssPropertyType::PaddingLeft,
compact_i16 = get_padding_left_raw
);
get_css_property_pixel!(
get_css_padding_right,
get_padding_right,
azul_css::props::property::CssPropertyType::PaddingRight,
compact_i16 = get_padding_right_raw
);
get_css_property_pixel!(
get_css_padding_top,
get_padding_top,
azul_css::props::property::CssPropertyType::PaddingTop,
compact_i16 = get_padding_top_raw
);
get_css_property_pixel!(
get_css_padding_bottom,
get_padding_bottom,
azul_css::props::property::CssPropertyType::PaddingBottom,
compact_i16 = get_padding_bottom_raw
);
get_css_property!(
get_css_min_width,
get_min_width,
LayoutMinWidth,
azul_css::props::property::CssPropertyType::MinWidth,
compact_u32_struct = get_min_width_raw
);
get_css_property!(
get_css_min_height,
get_min_height,
LayoutMinHeight,
azul_css::props::property::CssPropertyType::MinHeight,
compact_u32_struct = get_min_height_raw
);
get_css_property!(
get_css_max_width,
get_max_width,
LayoutMaxWidth,
azul_css::props::property::CssPropertyType::MaxWidth,
compact_u32_struct = get_max_width_raw
);
get_css_property!(
get_css_max_height,
get_max_height,
LayoutMaxHeight,
azul_css::props::property::CssPropertyType::MaxHeight,
compact_u32_struct = get_max_height_raw
);
get_css_property_pixel!(
get_css_border_left_width,
get_border_left_width,
azul_css::props::property::CssPropertyType::BorderLeftWidth,
compact_i16 = get_border_left_width_raw
);
get_css_property_pixel!(
get_css_border_right_width,
get_border_right_width,
azul_css::props::property::CssPropertyType::BorderRightWidth,
compact_i16 = get_border_right_width_raw
);
get_css_property_pixel!(
get_css_border_top_width,
get_border_top_width,
azul_css::props::property::CssPropertyType::BorderTopWidth,
compact_i16 = get_border_top_width_raw
);
get_css_property_pixel!(
get_css_border_bottom_width,
get_border_bottom_width,
azul_css::props::property::CssPropertyType::BorderBottomWidth,
compact_i16 = get_border_bottom_width_raw
);
pub fn get_break_before(styled_dom: &StyledDom, dom_id: Option<NodeId>) -> PageBreak {
let Some(id) = dom_id else {
return PageBreak::Auto;
};
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
if !cc.has_break(id.index()) {
return PageBreak::Auto;
}
}
}
let node_data = &styled_dom.node_data.as_container()[id];
styled_dom
.css_property_cache
.ptr
.get_break_before(node_data, &id, node_state)
.and_then(|v| v.get_property().cloned())
.unwrap_or(PageBreak::Auto)
}
pub fn get_break_after(styled_dom: &StyledDom, dom_id: Option<NodeId>) -> PageBreak {
let Some(id) = dom_id else {
return PageBreak::Auto;
};
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
if !cc.has_break(id.index()) {
return PageBreak::Auto;
}
}
}
let node_data = &styled_dom.node_data.as_container()[id];
styled_dom
.css_property_cache
.ptr
.get_break_after(node_data, &id, node_state)
.and_then(|v| v.get_property().cloned())
.unwrap_or(PageBreak::Auto)
}
pub fn is_forced_page_break(page_break: PageBreak) -> bool {
matches!(
page_break,
PageBreak::Always
| PageBreak::Page
| PageBreak::Left
| PageBreak::Right
| PageBreak::Recto
| PageBreak::Verso
| PageBreak::All
)
}
pub fn get_break_inside(styled_dom: &StyledDom, dom_id: Option<NodeId>) -> BreakInside {
let Some(id) = dom_id else {
return BreakInside::Auto;
};
let node_data = &styled_dom.node_data.as_container()[id];
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
styled_dom
.css_property_cache
.ptr
.get_break_inside(node_data, &id, node_state)
.and_then(|v| v.get_property().cloned())
.unwrap_or(BreakInside::Auto)
}
pub fn get_orphans(styled_dom: &StyledDom, dom_id: Option<NodeId>) -> u32 {
let Some(id) = dom_id else {
return 2; };
let node_data = &styled_dom.node_data.as_container()[id];
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
styled_dom
.css_property_cache
.ptr
.get_orphans(node_data, &id, node_state)
.and_then(|v| v.get_property().cloned())
.map(|o| o.inner)
.unwrap_or(2)
}
pub fn get_widows(styled_dom: &StyledDom, dom_id: Option<NodeId>) -> u32 {
let Some(id) = dom_id else {
return 2; };
let node_data = &styled_dom.node_data.as_container()[id];
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
styled_dom
.css_property_cache
.ptr
.get_widows(node_data, &id, node_state)
.and_then(|v| v.get_property().cloned())
.map(|w| w.inner)
.unwrap_or(2)
}
pub fn get_box_decoration_break(
styled_dom: &StyledDom,
dom_id: Option<NodeId>,
) -> BoxDecorationBreak {
let Some(id) = dom_id else {
return BoxDecorationBreak::Slice;
};
let node_data = &styled_dom.node_data.as_container()[id];
let node_state = &styled_dom.styled_nodes.as_container()[id].styled_node_state;
styled_dom
.css_property_cache
.ptr
.get_box_decoration_break(node_data, &id, node_state)
.and_then(|v| v.get_property().cloned())
.unwrap_or(BoxDecorationBreak::Slice)
}
pub fn is_avoid_page_break(page_break: &PageBreak) -> bool {
matches!(page_break, PageBreak::Avoid | PageBreak::AvoidPage)
}
pub fn is_avoid_break_inside(break_inside: &BreakInside) -> bool {
matches!(
break_inside,
BreakInside::Avoid | BreakInside::AvoidPage | BreakInside::AvoidColumn
)
}
use std::collections::HashMap;
use rust_fontconfig::{
FcFontCache, FcWeight, FontFallbackChain, PatternMatch, UnicodeRange,
DEFAULT_UNICODE_FALLBACK_SCRIPTS,
};
use crate::text3::cache::{FontChainKey, FontChainKeyOrRef, FontSelector, FontStack, FontStyle};
#[derive(Debug, Clone)]
pub struct CollectedFontStacks {
pub font_stacks: Vec<Vec<FontSelector>>,
pub hash_to_index: HashMap<u64, usize>,
pub font_refs: HashMap<usize, azul_css::props::basic::font::FontRef>,
}
#[derive(Debug, Clone)]
pub struct ResolvedFontChains {
pub chains: HashMap<FontChainKeyOrRef, FontFallbackChain>,
}
impl ResolvedFontChains {
pub fn get(&self, key: &FontChainKeyOrRef) -> Option<&FontFallbackChain> {
self.chains.get(key)
}
pub fn get_by_chain_key(&self, key: &FontChainKey) -> Option<&FontFallbackChain> {
self.chains.get(&FontChainKeyOrRef::Chain(key.clone()))
}
pub fn get_for_font_stack(&self, font_stack: &[FontSelector]) -> Option<&FontFallbackChain> {
let key = FontChainKeyOrRef::Chain(FontChainKey::from_selectors(font_stack));
self.chains.get(&key)
}
pub fn get_for_font_ref(&self, ptr: usize) -> Option<&FontFallbackChain> {
self.chains.get(&FontChainKeyOrRef::Ref(ptr))
}
pub fn into_inner(self) -> HashMap<FontChainKeyOrRef, FontFallbackChain> {
self.chains
}
pub fn into_fontconfig_chains(self) -> HashMap<FontChainKey, FontFallbackChain> {
self.chains
.into_iter()
.filter_map(|(key, chain)| {
match key {
FontChainKeyOrRef::Chain(chain_key) => Some((chain_key, chain)),
FontChainKeyOrRef::Ref(_) => None,
}
})
.collect()
}
pub fn len(&self) -> usize {
self.chains.len()
}
pub fn is_empty(&self) -> bool {
self.chains.is_empty()
}
pub fn font_refs_len(&self) -> usize {
self.chains.keys().filter(|k| k.is_ref()).count()
}
}
pub fn collect_font_stacks_from_styled_dom(
styled_dom: &StyledDom,
platform: &azul_css::system::Platform,
) -> CollectedFontStacks {
use azul_css::compact_cache::{FONT_WEIGHT_SHIFT, FONT_WEIGHT_MASK, FONT_STYLE_SHIFT, FONT_STYLE_MASK};
let mut font_stacks = Vec::new();
let mut hash_to_index: HashMap<u64, usize> = HashMap::new();
let mut font_refs: HashMap<usize, azul_css::props::basic::font::FontRef> = HashMap::new();
let node_data = styled_dom.node_data.as_container();
let cache = &styled_dom.css_property_cache.ptr;
let compact = match cache.compact_cache.as_ref() {
Some(c) => c,
None => return CollectedFontStacks { font_stacks, hash_to_index, font_refs },
};
let mut unique_font_keys: HashMap<(u64, u8, u8), usize> = HashMap::new();
let node_count = node_data.internal.len();
for i in 0..node_count {
if !matches!(node_data.internal[i].node_type, NodeType::Text(_)) {
continue;
}
let fh = compact.tier2b_text[i].font_family_hash;
let t1 = compact.tier1_enums[i];
let weight_bits = ((t1 >> FONT_WEIGHT_SHIFT) & FONT_WEIGHT_MASK) as u8;
let style_bits = ((t1 >> FONT_STYLE_SHIFT) & FONT_STYLE_MASK) as u8;
let key = (fh, weight_bits, style_bits);
unique_font_keys.entry(key).or_insert(i);
}
let styled_nodes = styled_dom.styled_nodes.as_container();
for (&(fh, _wb, _sb), &repr_idx) in &unique_font_keys {
let dom_id = match NodeId::from_usize(repr_idx) {
Some(id) => id,
None => continue,
};
let node_state = &styled_nodes[dom_id].styled_node_state;
let font_families = compact.font_hash_to_families
.get(&fh)
.cloned()
.unwrap_or_else(|| {
StyleFontFamilyVec::from_vec(vec![StyleFontFamily::System("serif".into())])
});
if let Some(first_family) = font_families.get(0) {
if let StyleFontFamily::Ref(font_ref) = first_family {
let ptr = font_ref.parsed as usize;
font_refs.entry(ptr).or_insert_with(|| font_ref.clone());
continue;
}
}
let font_weight = match get_font_weight_property(styled_dom, dom_id, node_state) {
MultiValue::Exact(v) => v,
_ => StyleFontWeight::Normal,
};
let font_style = match get_font_style_property(styled_dom, dom_id, node_state) {
MultiValue::Exact(v) => v,
_ => StyleFontStyle::Normal,
};
let fc_weight = super::fc::convert_font_weight(font_weight);
let fc_style = super::fc::convert_font_style(font_style);
let mut font_stack = Vec::with_capacity(font_families.len() + 3);
for i in 0..font_families.len() {
let family = font_families.get(i).unwrap();
if matches!(family, StyleFontFamily::Ref(_)) {
continue;
}
if let StyleFontFamily::SystemType(system_type) = family {
let font_names = system_type.get_fallback_chain(platform);
let system_weight = if system_type.is_bold() { FcWeight::Bold } else { fc_weight };
let system_style = if system_type.is_italic() { FontStyle::Italic } else { fc_style };
for font_name in font_names {
font_stack.push(FontSelector {
family: font_name.to_string(),
weight: system_weight,
style: system_style,
unicode_ranges: Vec::new(),
});
}
} else {
font_stack.push(FontSelector {
family: family.as_string(),
weight: fc_weight,
style: fc_style,
unicode_ranges: Vec::new(),
});
}
}
for fallback in &["sans-serif", "serif", "monospace"] {
if !font_stack.iter().any(|f| f.family.eq_ignore_ascii_case(fallback)) {
font_stack.push(FontSelector {
family: fallback.to_string(),
weight: FcWeight::Normal,
style: FontStyle::Normal,
unicode_ranges: Vec::new(),
});
}
}
if font_stack.is_empty() {
continue;
}
let key = FontChainKey::from_selectors(&font_stack);
let hash = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
key.hash(&mut hasher);
hasher.finish()
};
if !hash_to_index.contains_key(&hash) {
let idx = font_stacks.len();
font_stacks.push(font_stack);
hash_to_index.insert(hash, idx);
}
}
CollectedFontStacks {
font_stacks,
hash_to_index,
font_refs,
}
}
pub fn collect_used_codepoints(
styled_dom: &StyledDom,
) -> std::collections::BTreeSet<u32> {
let mut out = std::collections::BTreeSet::new();
let node_data = styled_dom.node_data.as_container();
for node in node_data.internal.iter() {
let azul_core::dom::NodeType::Text(s) = &node.node_type else {
continue;
};
for c in s.as_str().chars() {
let cp = c as u32;
if cp >= 0x80 {
out.insert(cp);
}
}
}
out
}
pub fn collect_used_codepoints_all(
styled_dom: &StyledDom,
) -> std::collections::BTreeSet<char> {
let mut out = std::collections::BTreeSet::new();
let node_data = styled_dom.node_data.as_container();
for node in node_data.internal.iter() {
let azul_core::dom::NodeType::Text(s) = &node.node_type else {
continue;
};
for c in s.as_str().chars() {
out.insert(c);
}
}
out
}
pub fn prune_chain_to_used_chars(
chain: &mut rust_fontconfig::FontFallbackChain,
used_chars: &std::collections::BTreeSet<u32>,
) {
fn fm_covers(fm: &rust_fontconfig::FontMatch, cp: u32) -> bool {
fm.unicode_ranges
.iter()
.any(|r| cp >= r.start && cp <= r.end)
}
for group in &mut chain.css_fallbacks {
if group.fonts.is_empty() {
continue;
}
let mut needed: Vec<u32> = used_chars.iter().copied().collect();
needed.retain(|&cp| !fm_covers(&group.fonts[0], cp));
let mut keep = 1;
for fm in group.fonts.iter().skip(1) {
if needed.is_empty() {
break;
}
keep += 1;
needed.retain(|&cp| !fm_covers(fm, cp));
}
group.fonts.truncate(keep);
}
chain.unicode_fallbacks.retain(|fm| {
used_chars.iter().any(|&cp| fm_covers(fm, cp))
});
}
pub fn scripts_present_in_styled_dom(styled_dom: &StyledDom) -> Vec<UnicodeRange> {
let scripts = DEFAULT_UNICODE_FALLBACK_SCRIPTS;
let mut seen = vec![false; scripts.len()];
let mut hits = 0usize;
let node_data = styled_dom.node_data.as_container();
'outer: for node in node_data.internal.iter() {
let text: &str = match &node.node_type {
azul_core::dom::NodeType::Text(s) => s.as_str(),
_ => continue,
};
for c in text.chars() {
let cp = c as u32;
if cp < 0x0400 {
continue;
}
for (idx, r) in scripts.iter().enumerate() {
if !seen[idx] && cp >= r.start && cp <= r.end {
seen[idx] = true;
hits += 1;
if hits == scripts.len() {
break 'outer;
}
break;
}
}
}
}
scripts
.iter()
.enumerate()
.filter_map(|(i, r)| if seen[i] { Some(*r) } else { None })
.collect()
}
pub fn resolve_font_chains(
collected: &CollectedFontStacks,
fc_cache: &FcFontCache,
scripts_hint: Option<&[UnicodeRange]>,
) -> ResolvedFontChains {
resolve_font_chains_with_registry(collected, fc_cache, None, scripts_hint)
}
pub fn resolve_font_chains_with_registry(
collected: &CollectedFontStacks,
fc_cache: &FcFontCache,
registry: Option<&rust_fontconfig::registry::FcFontRegistry>,
scripts_hint: Option<&[UnicodeRange]>,
) -> ResolvedFontChains {
let mut chains = HashMap::new();
for font_stack in &collected.font_stacks {
if font_stack.is_empty() {
continue;
}
let font_families: Vec<String> = font_stack
.iter()
.map(|s| s.family.clone())
.filter(|f| !f.is_empty())
.collect();
let font_families = if font_families.is_empty() {
vec!["sans-serif".to_string()]
} else {
font_families
};
let weight = font_stack[0].weight;
let is_italic = font_stack[0].style == FontStyle::Italic;
let is_oblique = font_stack[0].style == FontStyle::Oblique;
let cache_key = FontChainKeyOrRef::Chain(FontChainKey {
font_families: font_families.clone(),
weight,
italic: is_italic,
oblique: is_oblique,
});
if chains.contains_key(&cache_key) {
continue;
}
let italic = if is_italic {
PatternMatch::True
} else {
PatternMatch::False
};
let oblique = if is_oblique {
PatternMatch::True
} else {
PatternMatch::False
};
let chain = if let Some(reg) = registry {
reg.request_and_resolve_with_scripts(
&font_families, weight, italic, oblique, scripts_hint,
)
} else {
let mut trace = Vec::new();
fc_cache.resolve_font_chain_with_scripts(
&font_families, weight, italic, oblique, scripts_hint, &mut trace,
)
};
chains.insert(cache_key, chain);
}
ResolvedFontChains { chains }
}
pub fn collect_and_resolve_font_chains_with_registration<T: crate::font_traits::ParsedFontTrait>(
styled_dom: &StyledDom,
fc_cache: &FcFontCache,
font_manager: &crate::text3::cache::FontManager<T>,
platform: &azul_css::system::Platform,
) -> ResolvedFontChains {
let collected = collect_font_stacks_from_styled_dom(styled_dom, platform);
for (_ptr, font_ref) in &collected.font_refs {
font_manager.register_embedded_font(font_ref);
}
if let Some(registry) = font_manager.registry.as_deref() {
let used_chars = collect_used_codepoints_all(styled_dom);
if !used_chars.is_empty() {
return resolve_font_chains_fast(
&collected,
registry,
&used_chars,
);
}
}
let scripts = scripts_present_in_styled_dom(styled_dom);
let mut resolved = resolve_font_chains_with_registry(
&collected,
fc_cache,
font_manager.registry.as_deref(),
Some(&scripts),
);
let used_chars = collect_used_codepoints(styled_dom);
for chain in resolved.chains.values_mut() {
prune_chain_to_used_chars(chain, &used_chars);
}
resolved
}
pub fn resolve_font_chains_fast(
collected: &CollectedFontStacks,
registry: &rust_fontconfig::registry::FcFontRegistry,
codepoints: &std::collections::BTreeSet<char>,
) -> ResolvedFontChains {
use rust_fontconfig::PatternMatch;
static DBG: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
let dbg = *DBG.get_or_init(|| std::env::var_os("AZ_FAST_RESOLVE_DEBUG").is_some());
let mut chains: HashMap<FontChainKeyOrRef, rust_fontconfig::FontFallbackChain> =
HashMap::new();
for font_stack in &collected.font_stacks {
if font_stack.is_empty() {
continue;
}
let font_families: Vec<String> = font_stack
.iter()
.map(|s| s.family.clone())
.filter(|f| !f.is_empty())
.collect();
let font_families = if font_families.is_empty() {
vec!["sans-serif".to_string()]
} else {
font_families
};
let weight = font_stack[0].weight;
let is_italic = font_stack[0].style == FontStyle::Italic;
let is_oblique = font_stack[0].style == FontStyle::Oblique;
let cache_key = FontChainKeyOrRef::Chain(FontChainKey {
font_families: font_families.clone(),
weight,
italic: is_italic,
oblique: is_oblique,
});
if chains.contains_key(&cache_key) {
continue;
}
let italic_match = if is_italic {
PatternMatch::True
} else {
PatternMatch::False
};
let request = vec![(font_families.clone(), codepoints.clone())];
let mut chains_out = registry.request_fonts_fast(&request, weight, italic_match);
if dbg {
let total_fonts: usize = chains_out
.iter()
.map(|c| c.css_fallbacks.iter().map(|g| g.fonts.len()).sum::<usize>())
.sum();
eprintln!(
"[FAST] stack {:?} w={:?} i={:?} → {} groups, {} faces",
font_families,
weight,
italic_match,
chains_out.first().map(|c| c.css_fallbacks.len()).unwrap_or(0),
total_fonts,
);
}
if let Some(chain) = chains_out.pop() {
chains.insert(cache_key, chain);
}
}
ResolvedFontChains { chains }
}
pub fn collect_and_resolve_font_chains(
styled_dom: &StyledDom,
fc_cache: &FcFontCache,
platform: &azul_css::system::Platform,
) -> ResolvedFontChains {
let collected = collect_font_stacks_from_styled_dom(styled_dom, platform);
resolve_font_chains(&collected, fc_cache, None)
}
pub fn register_embedded_fonts_from_styled_dom<T: crate::font_traits::ParsedFontTrait>(
styled_dom: &StyledDom,
font_manager: &crate::text3::cache::FontManager<T>,
platform: &azul_css::system::Platform,
) {
let collected = collect_font_stacks_from_styled_dom(styled_dom, platform);
for (_ptr, font_ref) in &collected.font_refs {
font_manager.register_embedded_font(font_ref);
}
}
use std::collections::HashSet;
use rust_fontconfig::FontId;
pub fn collect_font_ids_from_chains(chains: &ResolvedFontChains) -> HashSet<FontId> {
let mut font_ids = HashSet::new();
if chains.chains.is_empty() {
return font_ids;
}
for chain in chains.chains.values() {
for group in &chain.css_fallbacks {
for font in &group.fonts {
font_ids.insert(font.id);
}
}
for font in &chain.unicode_fallbacks {
font_ids.insert(font.id);
}
}
font_ids
}
pub fn compute_fonts_to_load(
required_fonts: &HashSet<FontId>,
already_loaded: &HashSet<FontId>,
) -> HashSet<FontId> {
if required_fonts.is_empty() {
return HashSet::new();
}
required_fonts.difference(already_loaded).cloned().collect()
}
#[derive(Debug)]
pub struct FontLoadResult<T> {
pub loaded: HashMap<FontId, T>,
pub failed: Vec<(FontId, String)>,
}
pub fn load_fonts_from_disk<T, F>(
font_ids: &HashSet<FontId>,
fc_cache: &FcFontCache,
load_fn: F,
) -> FontLoadResult<T>
where
F: Fn(std::sync::Arc<rust_fontconfig::FontBytes>, usize) -> Result<T, crate::text3::cache::LayoutError>,
{
let mut loaded = HashMap::new();
let mut failed = Vec::new();
for font_id in font_ids {
let font_bytes = match fc_cache.get_font_bytes(font_id) {
Some(bytes) => bytes,
None => {
failed.push((
*font_id,
format!("Could not get font bytes for {:?}", font_id),
));
continue;
}
};
let font_index = fc_cache
.get_font_by_id(font_id)
.and_then(|source| match source {
rust_fontconfig::OwnedFontSource::Disk(path) => Some(path.font_index),
rust_fontconfig::OwnedFontSource::Memory(font) => Some(font.font_index),
})
.unwrap_or(0) as usize;
match load_fn(font_bytes, font_index) {
Ok(font) => {
loaded.insert(*font_id, font);
}
Err(e) => {
failed.push((
*font_id,
format!("Failed to parse font {:?}: {:?}", font_id, e),
));
}
}
}
FontLoadResult { loaded, failed }
}
pub fn resolve_and_load_fonts<T, F>(
styled_dom: &StyledDom,
fc_cache: &FcFontCache,
already_loaded: &HashSet<FontId>,
load_fn: F,
platform: &azul_css::system::Platform,
) -> (ResolvedFontChains, FontLoadResult<T>)
where
F: Fn(std::sync::Arc<rust_fontconfig::FontBytes>, usize) -> Result<T, crate::text3::cache::LayoutError>,
{
let chains = collect_and_resolve_font_chains(styled_dom, fc_cache, platform);
let required_fonts = collect_font_ids_from_chains(&chains);
let fonts_to_load = compute_fonts_to_load(&required_fonts, already_loaded);
let load_result = load_fonts_from_disk(&fonts_to_load, fc_cache, load_fn);
(chains, load_result)
}
use azul_css::props::style::scrollbar::{
LayoutScrollbarWidth, ScrollbarVisibilityMode,
StyleScrollbarColor,
};
#[derive(Debug, Clone)]
pub struct ComputedScrollbarStyle {
pub width_mode: LayoutScrollbarWidth,
pub visual_width_px: f32,
pub reserve_width_px: f32,
pub thumb_color: ColorU,
pub track_color: ColorU,
pub button_color: ColorU,
pub corner_color: ColorU,
pub clip_to_container_border: bool,
pub fade_delay_ms: u32,
pub fade_duration_ms: u32,
pub visibility: ScrollbarVisibilityMode,
pub show_scroll_buttons: bool,
pub scroll_button_size_px: f32,
pub show_corner_rect: bool,
pub thumb_color_hover: Option<ColorU>,
pub thumb_color_active: Option<ColorU>,
pub track_color_hover: Option<ColorU>,
pub visual_width_px_hover: Option<f32>,
pub visual_width_px_active: Option<f32>,
}
impl Default for ComputedScrollbarStyle {
fn default() -> Self {
let ctx = azul_css::dynamic_selector::DynamicSelectorContext::default();
let ua = azul_core::ua_css::evaluate_ua_scrollbar_css(&ctx);
Self::from_ua_resolved(&ua)
}
}
impl ComputedScrollbarStyle {
fn from_ua_resolved(ua: &azul_core::ua_css::ResolvedUaScrollbar) -> Self {
let width_mode = ua.width;
let visibility = ua.visibility;
let fade_delay_ms = ua.fade_delay.ms;
let fade_duration_ms = ua.fade_duration.ms;
let visual_width_px = match width_mode {
LayoutScrollbarWidth::Thin => 8.0,
LayoutScrollbarWidth::Auto => 12.0,
LayoutScrollbarWidth::None => 0.0,
};
let reserve_width_px = if visibility == ScrollbarVisibilityMode::WhenScrolling {
0.0
} else {
visual_width_px
};
let clip = visibility == ScrollbarVisibilityMode::WhenScrolling;
let is_overlay = visibility == ScrollbarVisibilityMode::WhenScrolling;
let show_scroll_buttons = !is_overlay;
let scroll_button_size_px = if is_overlay { 0.0 } else { visual_width_px };
let show_corner_rect = !is_overlay;
let (thumb_color, track_color) = match ua.color {
StyleScrollbarColor::Custom(c) => (c.thumb, c.track),
_ => (ColorU::TRANSPARENT, ColorU::TRANSPARENT),
};
let thumb_hover = ColorU {
r: thumb_color.r.saturating_add(30),
g: thumb_color.g.saturating_add(30),
b: thumb_color.b.saturating_add(30),
a: thumb_color.a.saturating_add(40).min(255),
};
let thumb_active = ColorU {
r: thumb_color.r.saturating_sub(15),
g: thumb_color.g.saturating_sub(15),
b: thumb_color.b.saturating_sub(15),
a: 255, };
let track_hover = ColorU {
r: track_color.r,
g: track_color.g,
b: track_color.b,
a: track_color.a.saturating_add(40).min(255),
};
let hover_width = visual_width_px + 4.0;
let active_width = visual_width_px + 4.0;
Self {
width_mode,
visual_width_px,
reserve_width_px,
thumb_color,
track_color,
button_color: ColorU::TRANSPARENT,
corner_color: ColorU::TRANSPARENT,
clip_to_container_border: clip,
fade_delay_ms,
fade_duration_ms,
visibility,
show_scroll_buttons,
scroll_button_size_px,
show_corner_rect,
thumb_color_hover: Some(thumb_hover),
thumb_color_active: Some(thumb_active),
track_color_hover: Some(track_hover),
visual_width_px_hover: Some(hover_width),
visual_width_px_active: Some(active_width),
}
}
}
pub fn get_scrollbar_style(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
system_style: Option<&azul_css::system::SystemStyle>,
) -> ComputedScrollbarStyle {
let node_data = &styled_dom.node_data.as_container()[node_id];
let ctx = match system_style {
Some(sys) => {
azul_css::dynamic_selector::DynamicSelectorContext::from_system_style(sys)
}
None => azul_css::dynamic_selector::DynamicSelectorContext::default(),
};
let ua = azul_core::ua_css::evaluate_ua_scrollbar_css(&ctx);
let result = ComputedScrollbarStyle::from_ua_resolved(&ua);
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
if !cc.has_scrollbar_css(node_id.index()) {
return result;
}
}
}
let mut result = result;
if let Some(track) = styled_dom
.css_property_cache
.ptr
.get_scrollbar_track(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
{
result.track_color = extract_color_from_background(track);
}
if let Some(thumb) = styled_dom
.css_property_cache
.ptr
.get_scrollbar_thumb(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
{
result.thumb_color = extract_color_from_background(thumb);
}
if let Some(button) = styled_dom
.css_property_cache
.ptr
.get_scrollbar_button(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
{
result.button_color = extract_color_from_background(button);
}
if let Some(corner) = styled_dom
.css_property_cache
.ptr
.get_scrollbar_corner(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
{
result.corner_color = extract_color_from_background(corner);
}
if let Some(scrollbar_width) = styled_dom
.css_property_cache
.ptr
.get_scrollbar_width(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
{
result.width_mode = *scrollbar_width;
let w = match scrollbar_width {
LayoutScrollbarWidth::Auto => 12.0,
LayoutScrollbarWidth::Thin => 8.0,
LayoutScrollbarWidth::None => 0.0,
};
result.visual_width_px = w;
if result.visibility != ScrollbarVisibilityMode::WhenScrolling {
result.reserve_width_px = w;
}
}
if let Some(scrollbar_color) = styled_dom
.css_property_cache
.ptr
.get_scrollbar_color(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
{
match scrollbar_color {
StyleScrollbarColor::Auto => { }
StyleScrollbarColor::Custom(custom) => {
result.thumb_color = custom.thumb;
result.track_color = custom.track;
}
}
}
if let Some(vis) = styled_dom
.css_property_cache
.ptr
.get_scrollbar_visibility(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
{
result.visibility = *vis;
result.clip_to_container_border = *vis == ScrollbarVisibilityMode::WhenScrolling;
let is_overlay = *vis == ScrollbarVisibilityMode::WhenScrolling;
if is_overlay {
result.reserve_width_px = 0.0;
result.show_scroll_buttons = false;
result.scroll_button_size_px = 0.0;
result.show_corner_rect = false;
} else {
result.reserve_width_px = result.visual_width_px;
}
}
if let Some(delay) = styled_dom
.css_property_cache
.ptr
.get_scrollbar_fade_delay(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
{
result.fade_delay_ms = delay.ms;
}
if let Some(dur) = styled_dom
.css_property_cache
.ptr
.get_scrollbar_fade_duration(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
{
result.fade_duration_ms = dur.ms;
}
result
}
pub fn get_scrollbar_style_cached<T: crate::font_traits::ParsedFontTrait>(
ctx: &crate::solver3::LayoutContext<'_, T>,
node_id: NodeId,
node_state: &StyledNodeState,
) -> ComputedScrollbarStyle {
if let Some(s) = ctx.scrollbar_style_cache.borrow().get(&node_id) {
return s.clone();
}
let style = get_scrollbar_style(
ctx.styled_dom,
node_id,
node_state,
ctx.system_style.as_deref(),
);
ctx.scrollbar_style_cache.borrow_mut().insert(node_id, style.clone());
style
}
fn extract_color_from_background(
bg: &azul_css::props::style::background::StyleBackgroundContent,
) -> ColorU {
use azul_css::props::style::background::StyleBackgroundContent;
match bg {
StyleBackgroundContent::Color(c) => *c,
_ => ColorU::TRANSPARENT,
}
}
pub fn should_clip_scrollbar_to_border(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> bool {
let style = get_scrollbar_style(styled_dom, node_id, node_state, None);
style.clip_to_container_border
}
pub fn get_scrollbar_width_px(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> f32 {
let style = get_scrollbar_style(styled_dom, node_id, node_state, None);
style.visual_width_px
}
pub fn is_text_selectable(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> bool {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom
.css_property_cache
.ptr
.get_user_select(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.map(|us| *us != StyleUserSelect::None)
.unwrap_or(true) }
pub fn is_node_contenteditable(styled_dom: &StyledDom, node_id: NodeId) -> bool {
use azul_core::dom::AttributeType;
let node_data = &styled_dom.node_data.as_container()[node_id];
if node_data.is_contenteditable() {
return true;
}
node_data.attributes().as_ref().iter().any(|attr| {
matches!(attr, AttributeType::ContentEditable(true))
})
}
use azul_css::props::layout::text::LayoutTextJustify;
use azul_css::props::layout::table::{LayoutTableLayout, StyleBorderCollapse, StyleCaptionSide, StyleEmptyCells};
use azul_css::props::style::text::StyleHyphens;
use azul_css::props::style::text::StyleWordBreak;
use azul_css::props::style::text::StyleOverflowWrap;
use azul_css::props::style::text::StyleLineBreak;
use azul_css::props::style::text::StyleTextAlignLast;
use azul_css::props::style::effects::StyleCursor;
use azul_css::props::style::effects::StyleObjectFit;
use azul_css::props::style::effects::StyleObjectPosition;
use azul_css::props::style::effects::StyleAspectRatio;
use azul_css::props::style::effects::StyleTextOrientation;
impl ExtractPropertyValue<LayoutTextJustify> for CssProperty {
fn extract(&self) -> Option<LayoutTextJustify> {
match self {
Self::TextJustify(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleHyphens> for CssProperty {
fn extract(&self) -> Option<StyleHyphens> {
match self {
Self::Hyphens(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleWordBreak> for CssProperty {
fn extract(&self) -> Option<StyleWordBreak> {
match self {
Self::WordBreak(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleOverflowWrap> for CssProperty {
fn extract(&self) -> Option<StyleOverflowWrap> {
match self {
Self::OverflowWrap(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleLineBreak> for CssProperty {
fn extract(&self) -> Option<StyleLineBreak> {
match self {
Self::LineBreak(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleTextAlignLast> for CssProperty {
fn extract(&self) -> Option<StyleTextAlignLast> {
match self {
Self::TextAlignLast(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleObjectFit> for CssProperty {
fn extract(&self) -> Option<StyleObjectFit> {
match self {
Self::ObjectFit(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleTextOrientation> for CssProperty {
fn extract(&self) -> Option<StyleTextOrientation> {
match self {
Self::TextOrientation(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleObjectPosition> for CssProperty {
fn extract(&self) -> Option<StyleObjectPosition> {
match self {
Self::ObjectPosition(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleAspectRatio> for CssProperty {
fn extract(&self) -> Option<StyleAspectRatio> {
match self {
Self::AspectRatio(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<LayoutTableLayout> for CssProperty {
fn extract(&self) -> Option<LayoutTableLayout> {
match self {
Self::TableLayout(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleBorderCollapse> for CssProperty {
fn extract(&self) -> Option<StyleBorderCollapse> {
match self {
Self::BorderCollapse(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleCaptionSide> for CssProperty {
fn extract(&self) -> Option<StyleCaptionSide> {
match self {
Self::CaptionSide(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleEmptyCells> for CssProperty {
fn extract(&self) -> Option<StyleEmptyCells> {
match self {
Self::EmptyCells(CssPropertyValue::Exact(v)) => Some(*v),
_ => None,
}
}
}
impl ExtractPropertyValue<StyleCursor> for CssProperty {
fn extract(&self) -> Option<StyleCursor> {
match self {
Self::Cursor(CssPropertyValue::Exact(v)) => Some(v.clone()),
_ => None,
}
}
}
get_css_property!(
get_text_justify,
get_text_justify,
LayoutTextJustify,
CssPropertyType::TextJustify
);
get_css_property!(
get_hyphens,
get_hyphens,
StyleHyphens,
CssPropertyType::Hyphens
);
get_css_property!(
get_word_break,
get_word_break,
StyleWordBreak,
CssPropertyType::WordBreak
);
get_css_property!(
get_overflow_wrap,
get_overflow_wrap,
StyleOverflowWrap,
CssPropertyType::OverflowWrap
);
get_css_property!(
get_line_break,
get_line_break,
StyleLineBreak,
CssPropertyType::LineBreak
);
get_css_property!(
get_text_align_last,
get_text_align_last,
StyleTextAlignLast,
CssPropertyType::TextAlignLast
);
get_css_property!(
get_table_layout,
get_table_layout,
LayoutTableLayout,
CssPropertyType::TableLayout
);
get_css_property!(
get_border_collapse,
get_border_collapse,
StyleBorderCollapse,
CssPropertyType::BorderCollapse,
compact = get_border_collapse
);
get_css_property!(
get_caption_side,
get_caption_side,
StyleCaptionSide,
CssPropertyType::CaptionSide
);
get_css_property!(
get_empty_cells,
get_empty_cells,
StyleEmptyCells,
CssPropertyType::EmptyCells
);
get_css_property!(
get_cursor_property,
get_cursor,
StyleCursor,
CssPropertyType::Cursor
);
pub fn get_height_value(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<LayoutHeight> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_height(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_shape_inside(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::layout::shape::ShapeInside> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_shape_inside(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_shape_outside(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::layout::shape::ShapeOutside> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_shape_outside(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_line_height_value(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::text::StyleLineHeight> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_line_height(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_text_indent_value(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::text::StyleTextIndent> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_text_indent(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_column_count(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::layout::column::ColumnCount> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_column_count(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_initial_letter(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::text::StyleInitialLetter> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_initial_letter(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_line_clamp(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::text::StyleLineClamp> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_line_clamp(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_hanging_punctuation(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::text::StyleHangingPunctuation> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_hanging_punctuation(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_text_combine_upright(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::text::StyleTextCombineUpright> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_text_combine_upright(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_exclusion_margin(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> f32 {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_exclusion_margin(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.map(|v| v.inner.get() as f32)
.unwrap_or(0.0)
}
pub fn get_hyphenation_language(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::azul_exclusion::StyleHyphenationLanguage> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_hyphenation_language(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_border_spacing(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> azul_css::props::layout::table::LayoutBorderSpacing {
use azul_css::props::basic::pixel::PixelValue;
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
let h_raw = cc.get_border_spacing_h_raw(node_id.index());
let v_raw = cc.get_border_spacing_v_raw(node_id.index());
if h_raw < azul_css::compact_cache::I16_SENTINEL_THRESHOLD
&& v_raw < azul_css::compact_cache::I16_SENTINEL_THRESHOLD
{
return azul_css::props::layout::table::LayoutBorderSpacing {
horizontal: PixelValue::px(h_raw as f32 / 10.0),
vertical: PixelValue::px(v_raw as f32 / 10.0),
};
}
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_border_spacing(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
.unwrap_or_default()
}
pub fn get_opacity(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> f32 {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
let raw = cc.get_opacity_raw(node_id.index());
if raw == azul_css::compact_cache::OPACITY_SENTINEL {
return 1.0;
}
return (raw as f32) / 254.0;
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_opacity(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.map(|v| v.inner.normalized())
.unwrap_or(1.0)
}
pub fn get_filter(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::filter::StyleFilterVec> {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
if !cc.has_filter(node_id.index()) { return None; }
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_filter(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_backdrop_filter(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::filter::StyleFilterVec> {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
if !cc.has_backdrop_filter(node_id.index()) { return None; }
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_backdrop_filter(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
#[inline]
fn box_shadow_fast_bail(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> bool {
if !node_state.is_normal() { return false; }
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
return !cc.has_box_shadow(node_id.index());
}
false
}
pub fn get_box_shadow_left(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::box_shadow::StyleBoxShadow> {
if box_shadow_fast_bail(styled_dom, node_id, node_state) { return None; }
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_box_shadow_left(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.map(|v| (**v).clone())
}
pub fn get_box_shadow_right(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::box_shadow::StyleBoxShadow> {
if box_shadow_fast_bail(styled_dom, node_id, node_state) { return None; }
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_box_shadow_right(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.map(|v| (**v).clone())
}
pub fn get_box_shadow_top(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::box_shadow::StyleBoxShadow> {
if box_shadow_fast_bail(styled_dom, node_id, node_state) { return None; }
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_box_shadow_top(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.map(|v| (**v).clone())
}
pub fn get_box_shadow_bottom(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::box_shadow::StyleBoxShadow> {
if box_shadow_fast_bail(styled_dom, node_id, node_state) { return None; }
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_box_shadow_bottom(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.map(|v| (**v).clone())
}
pub fn get_text_shadow(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::box_shadow::StyleBoxShadow> {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
if !cc.has_text_shadow(node_id.index()) { return None; }
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_text_shadow(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.map(|v| (**v).clone())
}
pub fn get_transform(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::transform::StyleTransformVec> {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
if !cc.has_transform(node_id.index()) {
return None;
}
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_transform(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_counter_reset(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::content::CounterReset> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_counter_reset(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn get_counter_increment(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::style::content::CounterIncrement> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_counter_increment(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}
pub fn is_node_contenteditable_inherited(styled_dom: &StyledDom, node_id: NodeId) -> bool {
use azul_core::dom::AttributeType;
let node_data_container = styled_dom.node_data.as_container();
let hierarchy = styled_dom.node_hierarchy.as_container();
let mut current_node_id = Some(node_id);
while let Some(nid) = current_node_id {
let node_data = &node_data_container[nid];
if node_data.is_contenteditable() {
return true;
}
for attr in node_data.attributes().as_ref().iter() {
if let AttributeType::ContentEditable(is_editable) = attr {
return *is_editable;
}
}
current_node_id = hierarchy.get(nid).and_then(|h| h.parent_id());
}
false
}
pub fn find_contenteditable_ancestor(styled_dom: &StyledDom, node_id: NodeId) -> Option<NodeId> {
use azul_core::dom::AttributeType;
let node_data_container = styled_dom.node_data.as_container();
let hierarchy = styled_dom.node_hierarchy.as_container();
let mut current_node_id = Some(node_id);
while let Some(nid) = current_node_id {
let node_data = &node_data_container[nid];
if node_data.is_contenteditable() {
return Some(nid);
}
for attr in node_data.attributes().as_ref().iter() {
if let AttributeType::ContentEditable(is_editable) = attr {
if *is_editable {
return Some(nid);
} else {
return None;
}
}
}
current_node_id = hierarchy.get(nid).and_then(|h| h.parent_id());
}
None
}
macro_rules! get_css_property_value {
($fn_name:ident, $cache_method:ident, $ret_type:ty) => {
pub fn $fn_name(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<$ret_type> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom
.css_property_cache
.ptr
.$cache_method(node_data, &node_id, node_state)
.cloned()
}
};
}
get_css_property_value!(get_flex_direction_prop, get_flex_direction, LayoutFlexDirectionValue);
get_css_property_value!(get_flex_wrap_prop, get_flex_wrap, LayoutFlexWrapValue);
get_css_property_value!(get_flex_grow_prop, get_flex_grow, LayoutFlexGrowValue);
get_css_property_value!(get_flex_shrink_prop, get_flex_shrink, LayoutFlexShrinkValue);
get_css_property_value!(get_flex_basis_prop, get_flex_basis, LayoutFlexBasisValue);
get_css_property_value!(get_align_items_prop, get_align_items, LayoutAlignItemsValue);
get_css_property_value!(get_align_self_prop, get_align_self, LayoutAlignSelfValue);
get_css_property_value!(get_align_content_prop, get_align_content, LayoutAlignContentValue);
get_css_property_value!(get_justify_content_prop, get_justify_content, LayoutJustifyContentValue);
get_css_property_value!(get_justify_items_prop, get_justify_items, LayoutJustifyItemsValue);
get_css_property_value!(get_justify_self_prop, get_justify_self, LayoutJustifySelfValue);
get_css_property_value!(get_gap_prop, get_gap, LayoutGapValue);
get_css_property_value!(get_grid_template_rows_prop, get_grid_template_rows, LayoutGridTemplateRowsValue);
get_css_property_value!(get_grid_template_columns_prop, get_grid_template_columns, LayoutGridTemplateColumnsValue);
get_css_property_value!(get_grid_auto_rows_prop, get_grid_auto_rows, LayoutGridAutoRowsValue);
get_css_property_value!(get_grid_auto_columns_prop, get_grid_auto_columns, LayoutGridAutoColumnsValue);
get_css_property_value!(get_grid_auto_flow_prop, get_grid_auto_flow, LayoutGridAutoFlowValue);
get_css_property_value!(get_grid_column_prop, get_grid_column, LayoutGridColumnValue);
get_css_property_value!(get_grid_row_prop, get_grid_row, LayoutGridRowValue);
pub fn get_grid_template_areas_prop(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<GridTemplateAreas> {
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom
.css_property_cache
.ptr
.get_property(node_data, &node_id, node_state, &CssPropertyType::GridTemplateAreas)
.and_then(|p| {
if let CssProperty::GridTemplateAreas(v) = p {
v.get_property().cloned()
} else {
None
}
})
}
pub fn get_clip_path(
styled_dom: &StyledDom,
node_id: NodeId,
node_state: &StyledNodeState,
) -> Option<azul_css::props::layout::shape::ClipPath> {
if node_state.is_normal() {
if let Some(ref cc) = styled_dom.css_property_cache.ptr.compact_cache {
if !cc.has_clip_path(node_id.index()) {
return None;
}
}
}
let node_data = &styled_dom.node_data.as_container()[node_id];
styled_dom.css_property_cache.ptr
.get_clip_path(node_data, &node_id, node_state)
.and_then(|v| v.get_property())
.cloned()
}