Skip to main content

blitz_dom/
util.rs

1use crate::node::{Node, NodeData};
2use color::{AlphaColor, Srgb};
3use std::borrow::Cow;
4use style::color::AbsoluteColor;
5
6pub type Color = AlphaColor<Srgb>;
7
8/// Decode raw font bytes, decompressing WOFF/WOFF2 if the `woff` feature is enabled.
9/// Returns the original slice unchanged for TTF/OTF input, and also on decompression
10/// failure. With the `woff` feature disabled, all input passes through unchanged.
11pub fn decode_font_bytes(bytes: &[u8]) -> Cow<'_, [u8]> {
12    if bytes.len() < 4 {
13        return Cow::Borrowed(bytes);
14    }
15    match &bytes[0..4] {
16        #[cfg(feature = "woff")]
17        b"wOFF" => wuff::decompress_woff1(bytes)
18            .map(Cow::Owned)
19            .unwrap_or_else(|_| {
20                #[cfg(feature = "tracing")]
21                tracing::warn!("Failed to decompress woff1 font");
22                Cow::Borrowed(bytes)
23            }),
24        #[cfg(feature = "woff")]
25        b"wOF2" => wuff::decompress_woff2(bytes)
26            .map(Cow::Owned)
27            .unwrap_or_else(|_| {
28                #[cfg(feature = "tracing")]
29                tracing::warn!("Failed to decompress woff2 font");
30                Cow::Borrowed(bytes)
31            }),
32        _ => Cow::Borrowed(bytes),
33    }
34}
35
36#[cfg(feature = "svg")]
37use std::sync::{Arc, LazyLock};
38#[cfg(feature = "svg")]
39use usvg::fontdb;
40#[cfg(feature = "svg")]
41pub(crate) static FONT_DB: LazyLock<Arc<fontdb::Database>> = LazyLock::new(|| {
42    let mut db = fontdb::Database::new();
43    db.load_system_fonts();
44    Arc::new(db)
45});
46
47#[derive(Clone, Copy, Debug)]
48pub enum ImageType {
49    Image,
50    Background(usize),
51}
52
53/// A point
54#[derive(Clone, Debug, Copy, Eq, PartialEq)]
55pub struct Point<T> {
56    /// The x coordinate
57    pub x: T,
58    /// The y coordinate
59    pub y: T,
60}
61
62impl Point<f64> {
63    pub const ZERO: Self = Point { x: 0.0, y: 0.0 };
64}
65
66// Debug print an RcDom
67pub fn walk_tree(indent: usize, node: &Node) {
68    // Skip all-whitespace text nodes entirely
69    if let NodeData::Text(data) = &node.data {
70        if data.content.chars().all(|c| c.is_ascii_whitespace()) {
71            return;
72        }
73    }
74
75    print!("{}", " ".repeat(indent));
76    let id = node.id;
77    match &node.data {
78        NodeData::Document => println!("#Document {id}"),
79
80        NodeData::Text(data) => {
81            if data.content.chars().all(|c| c.is_ascii_whitespace()) {
82                println!("{id} #text: <whitespace>");
83            } else {
84                let content = data.content.trim();
85                if content.len() > 10 {
86                    println!(
87                        "#text {id}: {}...",
88                        content
89                            .split_at(content.char_indices().take(10).last().unwrap().0)
90                            .0
91                            .escape_default()
92                    )
93                } else {
94                    println!("#text {id}: {}", data.content.trim().escape_default())
95                }
96            }
97        }
98
99        NodeData::Comment => println!("<!-- COMMENT {id} -->"),
100
101        NodeData::AnonymousBlock(_) => println!("{id} AnonymousBlock"),
102
103        NodeData::Element(data) => {
104            print!("<{} {id}", data.name.local);
105            for attr in data.attrs.iter() {
106                print!(" {}=\"{}\"", attr.name.local, attr.value);
107            }
108            if !node.children.is_empty() {
109                println!(">");
110            } else {
111                println!("/>");
112            }
113        } // NodeData::Doctype {
114          //     ref name,
115          //     ref public_id,
116          //     ref system_id,
117          // } => println!("<!DOCTYPE {} \"{}\" \"{}\">", name, public_id, system_id),
118          // NodeData::ProcessingInstruction { .. } => unreachable!(),
119    }
120
121    if !node.children.is_empty() {
122        for child_id in node.children.iter() {
123            walk_tree(indent + 2, node.with(*child_id));
124        }
125
126        if let NodeData::Element(data) = &node.data {
127            println!("{}</{}>", " ".repeat(indent), data.name.local);
128        }
129    }
130}
131
132#[cfg(feature = "svg")]
133pub(crate) fn parse_svg(source: &[u8]) -> Result<usvg::Tree, usvg::Error> {
134    let options = usvg::Options {
135        fontdb: Arc::clone(&*FONT_DB),
136        ..Default::default()
137    };
138
139    let tree = usvg::Tree::from_data(source, &options)?;
140    Ok(tree)
141}
142
143pub trait ToColorColor {
144    /// Converts a color into the `AlphaColor<Srgb>` type from the `color` crate
145    fn as_color_color(&self) -> Color;
146}
147impl ToColorColor for AbsoluteColor {
148    fn as_color_color(&self) -> Color {
149        Color::new(
150            *self
151                .to_color_space(style::color::ColorSpace::Srgb)
152                .raw_components(),
153        )
154    }
155}
156
157/// Creates an markup5ever::QualName.
158/// Given a local name and an optional namespace
159#[macro_export]
160macro_rules! qual_name {
161    ($local:tt $(, $ns:ident)?) => {
162        $crate::QualName {
163            prefix: None,
164            ns: $crate::ns!($($ns)?),
165            local: $crate::local_name!($local),
166        }
167    };
168}