widgetry/
assets.rs

1use std::cell::RefCell;
2use std::collections::{HashMap, HashSet};
3use std::num::NonZeroUsize;
4
5use lru::LruCache;
6use usvg_text_layout::fontdb;
7
8use geom::Bounds;
9
10use crate::text::Font;
11use crate::{text, EventCtx, GeomBatch, GfxCtx, Prerender, Style};
12
13// TODO We don't need refcell maybe? Can we take &mut Assets?
14pub struct Assets {
15    pub default_line_height: RefCell<f64>,
16    text_cache: RefCell<LruCache<String, GeomBatch>>,
17    line_height_cache: RefCell<HashMap<(Font, usize), f64>>,
18    // Keyed by filename
19    svg_cache: RefCell<HashMap<String, (GeomBatch, Bounds)>>,
20    font_to_id: HashMap<Font, fontdb::ID>,
21    extra_fonts: RefCell<HashSet<String>>,
22    pub(crate) style: RefCell<Style>,
23    pub(crate) fontdb: RefCell<fontdb::Database>,
24    pub read_svg: Box<dyn Fn(&str) -> Vec<u8>>,
25    base_url: Option<String>,
26    are_gzipped: bool,
27}
28
29impl Assets {
30    pub fn new(
31        style: Style,
32        base_url: Option<String>,
33        are_gzipped: bool,
34        read_svg: Box<dyn Fn(&str) -> Vec<u8>>,
35    ) -> Assets {
36        // Many fonts are statically bundled with the library right now, on both native and web.
37        // ctx.is_font_loaded and ctx.load_font can be used to dynamically add more later.
38        let mut fontdb = fontdb::Database::new();
39        fontdb.load_font_data(include_bytes!("../fonts/BungeeInline-Regular.ttf").to_vec());
40        fontdb.load_font_data(include_bytes!("../fonts/Bungee-Regular.ttf").to_vec());
41        fontdb.load_font_data(include_bytes!("../fonts/Overpass-Bold.ttf").to_vec());
42        fontdb.load_font_data(include_bytes!("../fonts/OverpassMono-Bold.ttf").to_vec());
43        fontdb.load_font_data(include_bytes!("../fonts/Overpass-Regular.ttf").to_vec());
44        fontdb.load_font_data(include_bytes!("../fonts/Overpass-SemiBold.ttf").to_vec());
45
46        let mut font_to_id = HashMap::new();
47        for font in [
48            Font::BungeeInlineRegular,
49            Font::BungeeRegular,
50            Font::OverpassBold,
51            Font::OverpassRegular,
52            Font::OverpassSemiBold,
53            Font::OverpassMonoBold,
54        ] {
55            font_to_id.insert(
56                font,
57                fontdb
58                    .query(&fontdb::Query {
59                        families: &[fontdb::Family::Name(font.family())],
60                        weight: match font {
61                            Font::OverpassBold | Font::OverpassMonoBold => fontdb::Weight::BOLD,
62                            Font::OverpassSemiBold => fontdb::Weight::SEMIBOLD,
63                            _ => fontdb::Weight::NORMAL,
64                        },
65                        stretch: fontdb::Stretch::Normal,
66                        style: fontdb::Style::Normal,
67                    })
68                    .unwrap(),
69            );
70        }
71
72        let a = Assets {
73            default_line_height: RefCell::new(0.0),
74            text_cache: RefCell::new(LruCache::new(NonZeroUsize::new(500).unwrap())),
75            line_height_cache: RefCell::new(HashMap::new()),
76            svg_cache: RefCell::new(HashMap::new()),
77            font_to_id,
78            extra_fonts: RefCell::new(HashSet::new()),
79            fontdb: RefCell::new(fontdb),
80            style: RefCell::new(style),
81            base_url,
82            are_gzipped,
83            read_svg,
84        };
85        *a.default_line_height.borrow_mut() =
86            a.line_height(text::DEFAULT_FONT, text::DEFAULT_FONT_SIZE);
87        a
88    }
89
90    pub fn base_url(&self) -> Option<&str> {
91        self.base_url.as_deref()
92    }
93
94    pub fn are_gzipped(&self) -> bool {
95        self.are_gzipped
96    }
97
98    pub fn is_font_loaded(&self, filename: &str) -> bool {
99        self.extra_fonts.borrow().contains(filename)
100    }
101
102    pub fn load_font(&self, filename: &str, bytes: Vec<u8>) {
103        info!("Loaded extra font {}", filename);
104        self.extra_fonts.borrow_mut().insert(filename.to_string());
105        self.fontdb.borrow_mut().load_font_data(bytes);
106        // We don't need to fill out font_to_id, because we can't directly create text using this
107        // font.
108    }
109
110    pub fn line_height(&self, font: Font, font_size: usize) -> f64 {
111        let key = (font, font_size);
112        if let Some(height) = self.line_height_cache.borrow().get(&key) {
113            return *height;
114        }
115
116        // This seems to be missing line_gap, and line_gap is 0, so manually adjust here.
117        let line_height = self
118            .fontdb
119            .borrow()
120            .with_face_data(self.font_to_id[&font], |data, face_index| {
121                let font = ttf_parser::Face::parse(data, face_index).unwrap();
122                let units_per_em = font.units_per_em();
123                let ascent = font.ascender();
124                let descent = font.descender();
125                let scale = (font_size as f64) / (units_per_em as f64);
126                ((ascent - descent) as f64) * scale
127            })
128            .unwrap();
129        let height = text::SCALE_LINE_HEIGHT * line_height;
130
131        self.line_height_cache.borrow_mut().insert(key, height);
132        height
133    }
134
135    #[allow(clippy::ptr_arg)] // &[str] does not work with `LruCache`
136    pub fn get_cached_text(&self, key: &String) -> Option<GeomBatch> {
137        self.text_cache.borrow_mut().get(key).cloned()
138    }
139
140    pub fn cache_text(&self, key: String, geom: GeomBatch) {
141        self.text_cache.borrow_mut().put(key, geom);
142    }
143
144    pub fn clear_text_cache(&self) {
145        self.text_cache.borrow_mut().clear()
146    }
147
148    pub fn get_cached_svg(&self, key: &str) -> Option<(GeomBatch, Bounds)> {
149        self.svg_cache.borrow().get(key).cloned()
150    }
151
152    pub fn cache_svg(&self, key: String, geom: GeomBatch, bounds: Bounds) {
153        self.svg_cache.borrow_mut().insert(key, (geom, bounds));
154    }
155}
156
157impl std::convert::AsRef<Assets> for GfxCtx<'_> {
158    fn as_ref(&self) -> &Assets {
159        &self.prerender.assets
160    }
161}
162
163impl std::convert::AsRef<Assets> for EventCtx<'_> {
164    fn as_ref(&self) -> &Assets {
165        &self.prerender.assets
166    }
167}
168
169impl std::convert::AsRef<Assets> for Prerender {
170    fn as_ref(&self) -> &Assets {
171        &self.assets
172    }
173}
174
175impl std::convert::AsRef<Assets> for Assets {
176    fn as_ref(&self) -> &Assets {
177        self
178    }
179}