float-pigment-layout 0.8.2

A light-weight layout engine which supports common web layout algorithms.
Documentation
use crate::*;
use lru::LruCache;

const LAYOUT_LRU_CACHE_LEN_PER_NODE: usize = 8;

pub type CacheKeyRequestSize<L> = OptionSize<<L as LengthNum>::Hashable>;
pub type CacheKeyMaxContent<L> = OptionSize<<L as LengthNum>::Hashable>;
pub type CacheKeyParentSize<L> = OptionSize<<L as LengthNum>::Hashable>;
pub type CacheKeySizingMode = SizingMode;

#[cfg_attr(debug_assertions, derive(Debug))]
#[allow(clippy::type_complexity)]
pub(crate) struct LayoutComputeCache<L: LengthNum> {
    touched: bool,
    parent_size_affected: bool,
    size_cache: LruCache<
        (
            CacheKeyRequestSize<L>,
            CacheKeyMaxContent<L>,
            CacheKeyParentSize<L>,
            CacheKeySizingMode,
        ),
        ComputeResult<L>,
    >,
    position_cache: Option<(
        (
            CacheKeyRequestSize<L>,
            CacheKeyMaxContent<L>,
            CacheKeyParentSize<L>,
        ),
        ComputeResult<L>,
    )>,
}

impl<L: LengthNum> LayoutComputeCache<L> {
    pub(crate) fn new() -> Self {
        Self {
            touched: false,
            parent_size_affected: false,
            size_cache: LruCache::new(0),
            position_cache: None,
        }
    }

    pub(crate) fn calc_parent_size_affected(node: &impl LayoutTreeNode) -> bool {
        let style = node.style();
        if let DefLength::Percent(_) = style.min_width() {
            return true;
        };
        if let DefLength::Percent(_) = style.min_height() {
            return true;
        };
        if let DefLength::Percent(_) = style.max_width() {
            return true;
        };
        if let DefLength::Percent(_) = style.max_height() {
            return true;
        };
        if let DefLength::Percent(_) = style.margin_left() {
            return true;
        };
        if let DefLength::Percent(_) = style.margin_right() {
            return true;
        };
        if let DefLength::Percent(_) = style.margin_top() {
            return true;
        };
        if let DefLength::Percent(_) = style.margin_bottom() {
            return true;
        };
        if let DefLength::Percent(_) = style.padding_left() {
            return true;
        };
        if let DefLength::Percent(_) = style.padding_right() {
            return true;
        };
        if let DefLength::Percent(_) = style.padding_top() {
            return true;
        };
        if let DefLength::Percent(_) = style.padding_bottom() {
            return true;
        };
        if let DefLength::Percent(_) = style.border_left() {
            return true;
        };
        if let DefLength::Percent(_) = style.border_right() {
            return true;
        };
        if let DefLength::Percent(_) = style.border_top() {
            return true;
        };
        if let DefLength::Percent(_) = style.border_bottom() {
            return true;
        };
        false
    }

    pub(crate) fn clear(&mut self) -> bool {
        self.size_cache.clear();
        self.clear_position_cache();
        let ret = self.touched;
        self.touched = false;
        ret
    }

    #[inline(always)]
    pub(crate) fn clear_position_cache(&mut self) {
        self.position_cache = None;
    }

    pub(crate) fn touch(&mut self, node: &impl LayoutTreeNode) {
        if !self.touched {
            self.touched = true;
            self.parent_size_affected = Self::calc_parent_size_affected(node);
        }
    }

    pub(crate) fn gen_key_with_sizing_mode(
        &mut self,
        node: &impl LayoutTreeNode,
        req: &ComputeRequest<L>,
    ) -> (
        CacheKeyRequestSize<L>,
        CacheKeyMaxContent<L>,
        CacheKeyParentSize<L>,
        CacheKeySizingMode,
    ) {
        self.touch(node);
        let p = if self.parent_size_affected {
            *req.parent_inner_size
        } else {
            OptionSize::new(OptionNum::none(), OptionNum::none())
        };
        let size = Size::new(req.size.width.to_hashable(), req.size.height.to_hashable());
        let max_content = Size::new(
            req.max_content.width.to_hashable(),
            req.max_content.height.to_hashable(),
        );
        let p = Size::new(p.width.to_hashable(), p.height.to_hashable());
        (size, max_content, p, req.sizing_mode)
    }

    pub(crate) fn gen_key(
        &mut self,
        node: &impl LayoutTreeNode,
        req: &ComputeRequest<L>,
    ) -> (
        CacheKeyRequestSize<L>,
        CacheKeyMaxContent<L>,
        CacheKeyParentSize<L>,
    ) {
        self.touch(node);
        let p = if self.parent_size_affected {
            *req.parent_inner_size
        } else {
            OptionSize::new(OptionNum::none(), OptionNum::none())
        };
        let size = Size::new(req.size.width.to_hashable(), req.size.height.to_hashable());
        let max_content = Size::new(
            req.max_content.width.to_hashable(),
            req.max_content.height.to_hashable(),
        );
        let p = Size::new(p.width.to_hashable(), p.height.to_hashable());
        (size, max_content, p)
    }

    pub(crate) fn write_all_size_inner(
        &mut self,
        node: &impl LayoutTreeNode<Length = L>,
        req: &ComputeRequest<L>,
        result: ComputeResult<L>,
    ) {
        let key = self.gen_key_with_sizing_mode(node, req);
        if self.size_cache.cap() == 0 {
            self.size_cache.resize(LAYOUT_LRU_CACHE_LEN_PER_NODE);
        }
        self.size_cache.put(key, result);
    }

    pub(crate) fn write_all_size(
        &mut self,
        node: &impl LayoutTreeNode<Length = L>,
        req: &ComputeRequest<L>,
        result: ComputeResult<L>,
    ) {
        if req.parent_is_block {
            // do not store cache if parent is common block
            return;
        }
        self.write_all_size_inner(node, req, result);
        if req.size.width.is_none() || req.size.height.is_none() {
            let mut req = req.clone();
            req.size = Normalized(OptionSize::new(
                req.size.width.or(OptionNum::some(result.size.width)),
                req.size.height.or(OptionNum::some(result.size.height)),
            ));
            self.write_all_size_inner(node, &req, result);
            if req.max_content.width.is_none() || req.max_content.height.is_none() {
                req.max_content = Normalized(OptionSize::new(
                    req.max_content.width.or(OptionNum::some(result.size.width)),
                    req.max_content
                        .height
                        .or(OptionNum::some(result.size.height)),
                ));
                self.write_all_size_inner(node, &req, result);
            }
        }
        if req.max_content.width.is_none() || req.max_content.height.is_none() {
            let mut req = req.clone();
            req.max_content = Normalized(OptionSize::new(
                req.max_content.width.or(OptionNum::some(result.size.width)),
                req.max_content
                    .height
                    .or(OptionNum::some(result.size.height)),
            ));
            self.write_all_size_inner(node, &req, result);
        }
    }

    pub(crate) fn write_position<T: LayoutTreeNode<Length = L>>(
        &mut self,
        node: &T,
        req: &ComputeRequest<L>,
        result: ComputeResult<L>,
    ) {
        if !req.parent_is_block {
            // do not store cache if parent is common block
            self.write_all_size(node, req, result);
        } else if self.position_cache.is_none() {
            self.parent_size_affected = Self::calc_parent_size_affected(node);
        }
        let key = self.gen_key(node, req);
        self.position_cache = Some((key, result));
    }

    pub(crate) fn read(
        &mut self,
        node: &impl LayoutTreeNode<Length = L>,
        req: &ComputeRequest<L>,
    ) -> Option<ComputeResult<L>> {
        if !self.touched {
            return None;
        }
        if req.kind != ComputeRequestKind::Position {
            let key = self.gen_key_with_sizing_mode(node, req);
            self.size_cache.get(&key).cloned()
        } else {
            let key = self.gen_key(node, req);
            self.position_cache
                .as_ref()
                .and_then(|(k, res)| if *k == key { Some(*res) } else { None })
        }
    }
}