Skip to main content

azul_core/
resources.rs

1//! Resource management types for the application.
2//!
3//! This module contains the core types for managing application resources:
4//! - `AppConfig`: application-level configuration (logging, fonts, routes, components)
5//! - `ImageRef` / `ImageRefHash`: reference-counted decoded image handles
6//! - `FontKey` / `FontInstanceKey` / `ImageKey`: renderer-scoped resource keys
7//! - `RendererResources`: per-window font/image registry with frame-based GC
8//! - `RawImage`: CPU-side pixel data with format conversion to BGRA8
9//! - `build_add_font_resource_updates` / `build_add_image_resource_updates`:
10//!   diff current frame against registered resources and produce WebRender updates
11
12#[cfg(not(feature = "std"))]
13use alloc::string::ToString;
14use alloc::{boxed::Box, collections::btree_map::BTreeMap, string::String, vec::Vec};
15use core::{
16    fmt,
17    hash::{Hash, Hasher},
18    sync::atomic::{AtomicU64, AtomicUsize, Ordering as AtomicOrdering},
19};
20
21use azul_css::{
22    format_rust_code::GetHash,
23    props::basic::{
24        pixel::DEFAULT_FONT_SIZE, ColorU, FloatValue, FontRef, LayoutRect, LayoutSize,
25        StyleFontFamily, StyleFontFamilyVec, StyleFontSize,
26    },
27    system::SystemStyle,
28    AzString, F32Vec, LayoutDebugMessage, OptionI32, StringVec, U16Vec, U32Vec, U8Vec,
29};
30use rust_fontconfig::FcFontCache;
31
32// Re-export Core* callback types for public use
33pub use crate::callbacks::{
34    CoreImageCallback, CoreRenderImageCallback, CoreRenderImageCallbackType,
35};
36use crate::{
37    callbacks::{LayoutCallback, VirtualViewCallback},
38    dom::{DomId, NodeData, NodeType},
39    geom::{LogicalPosition, LogicalRect, LogicalSize},
40    gl::{OptionGlContextPtr, Texture},
41    hit_test::DocumentId,
42    id::NodeId,
43    prop_cache::CssPropertyCache,
44    refany::RefAny,
45    styled_dom::{
46        NodeHierarchyItemId, StyleFontFamiliesHash, StyleFontFamilyHash, StyledDom, StyledNodeState,
47    },
48    ui_solver::GlyphInstance,
49    window::{AzStringPair, OptionChar, StringPairVec},
50    xml::{
51        ComponentDef, ComponentDefVec, ComponentId, ComponentLibrary, ComponentLibraryVec,
52        ComponentSource, RegisterComponentFn, RegisterComponentLibraryFn,
53    },
54    FastBTreeSet, OrderedMap,
55};
56
57#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
58#[repr(C)]
59pub struct DpiScaleFactor {
60    pub inner: FloatValue,
61}
62
63impl DpiScaleFactor {
64    pub fn new(f: f32) -> Self {
65        Self {
66            inner: FloatValue::new(f),
67        }
68    }
69}
70
71/// Determines what happens when all application windows are closed
72#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
73#[repr(C)]
74pub enum AppTerminationBehavior {
75    /// Return control to main() when all windows are closed (if platform supports it).
76    /// On macOS, this exits the NSApplication run loop and returns to main().
77    /// This is useful if you want to clean up resources or restart the event loop.
78    ReturnToMain,
79    /// Keep the application running even when all windows are closed.
80    /// This is the standard macOS behavior (app stays in dock until explicitly quit).
81    RunForever,
82    /// Immediately terminate the process when all windows are closed.
83    /// Calls std::process::exit(0).
84    EndProcess,
85}
86
87impl Default for AppTerminationBehavior {
88    fn default() -> Self {
89        // Default: End the process when all windows close (cross-platform behavior)
90        AppTerminationBehavior::EndProcess
91    }
92}
93
94/// A named font bundled with the application (name + raw bytes).
95/// The name is used to reference the font in CSS (e.g. `font-family: "MyFont"`).
96#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
97#[repr(C)]
98pub struct NamedFont {
99    /// The font family name to use in CSS (e.g. "Roboto", "MyCustomFont")
100    pub name: AzString,
101    /// Raw font file bytes (TTF, OTF, etc.)
102    pub bytes: U8Vec,
103}
104
105impl_option!(
106    NamedFont,
107    OptionNamedFont,
108    copy = false,
109    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
110);
111
112impl NamedFont {
113    pub fn new(name: AzString, bytes: U8Vec) -> Self {
114        Self { name, bytes }
115    }
116}
117
118impl_vec!(NamedFont, NamedFontVec, NamedFontVecDestructor, NamedFontVecDestructorType, NamedFontVecSlice, OptionNamedFont);
119impl_vec_mut!(NamedFont, NamedFontVec);
120impl_vec_debug!(NamedFont, NamedFontVec);
121impl_vec_partialeq!(NamedFont, NamedFontVec);
122impl_vec_eq!(NamedFont, NamedFontVec);
123impl_vec_partialord!(NamedFont, NamedFontVec);
124impl_vec_ord!(NamedFont, NamedFontVec);
125impl_vec_hash!(NamedFont, NamedFontVec);
126impl_vec_clone!(NamedFont, NamedFontVec, NamedFontVecDestructor);
127
128/// Configuration for how fonts should be loaded at app startup.
129#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
130#[repr(C, u8)]
131pub enum FontLoadingConfig {
132    /// Load all system fonts (default behavior, can be slow on systems with many fonts)
133    LoadAllSystemFonts,
134    /// Only load fonts for specific families (faster startup).
135    /// Generic families like "sans-serif" are automatically expanded to OS-specific fonts.
136    LoadOnlyFamilies(StringVec),
137    /// Don't load any system fonts, only use bundled fonts
138    BundledFontsOnly,
139}
140
141impl Default for FontLoadingConfig {
142    fn default() -> Self {
143        FontLoadingConfig::LoadAllSystemFonts
144    }
145}
146
147/// Mock environment for CSS evaluation.
148/// 
149/// Allows overriding auto-detected system properties for testing and development.
150/// Any field set to `None` will use the auto-detected value.
151/// Any field set to `Some(...)` will override the auto-detected value.
152/// 
153/// # Example
154/// ```rust
155/// # use azul_core::resources::CssMockEnvironment;
156/// use azul_css::dynamic_selector::{
157///     OsCondition, ThemeCondition, OsVersion,
158///     OptionOsCondition, OptionThemeCondition, OptionOsVersion,
159/// };
160/// 
161/// // Mock a Linux dark theme environment on any platform
162/// let mock = CssMockEnvironment {
163///     os: OptionOsCondition::Some(OsCondition::Linux),
164///     theme: OptionThemeCondition::Some(ThemeCondition::Dark),
165///     ..Default::default()
166/// };
167/// 
168/// // Mock Windows XP for retro testing
169/// let mock = CssMockEnvironment {
170///     os: OptionOsCondition::Some(OsCondition::Windows),
171///     os_version: OptionOsVersion::Some(OsVersion::WIN_XP),
172///     ..Default::default()
173/// };
174/// ```
175#[derive(Debug, Clone, Default)]
176#[repr(C)]
177pub struct CssMockEnvironment {
178    /// Override the current theme (light/dark)
179    pub theme: azul_css::dynamic_selector::OptionThemeCondition,
180    /// Override the current language (BCP 47 tag, e.g., "de-DE", "en-US")
181    pub language: azul_css::OptionString,
182    /// Override the detected OS version
183    pub os_version: azul_css::dynamic_selector::OptionOsVersion,
184    /// Override the detected operating system
185    pub os: azul_css::dynamic_selector::OptionOsCondition,
186    /// Override the Linux desktop environment (only applies when os = Linux)
187    pub desktop_env: azul_css::dynamic_selector::OptionLinuxDesktopEnv,
188    /// Override viewport dimensions (for @media queries)
189    /// Only use for testing - normally set by window size
190    pub viewport_width: azul_css::OptionF32,
191    pub viewport_height: azul_css::OptionF32,
192    /// Override the reduced motion preference
193    pub prefers_reduced_motion: azul_css::OptionBool,
194    /// Override the high contrast preference
195    pub prefers_high_contrast: azul_css::OptionBool,
196}
197
198impl CssMockEnvironment {
199    /// Create a mock for Linux environment
200    pub fn linux() -> Self {
201        Self {
202            os: azul_css::dynamic_selector::OptionOsCondition::Some(azul_css::dynamic_selector::OsCondition::Linux),
203            ..Default::default()
204        }
205    }
206    
207    /// Create a mock for Windows environment
208    pub fn windows() -> Self {
209        Self {
210            os: azul_css::dynamic_selector::OptionOsCondition::Some(azul_css::dynamic_selector::OsCondition::Windows),
211            ..Default::default()
212        }
213    }
214    
215    /// Create a mock for macOS environment
216    pub fn macos() -> Self {
217        Self {
218            os: azul_css::dynamic_selector::OptionOsCondition::Some(azul_css::dynamic_selector::OsCondition::MacOS),
219            ..Default::default()
220        }
221    }
222    
223    /// Create a mock for dark theme
224    pub fn dark_theme() -> Self {
225        Self {
226            theme: azul_css::dynamic_selector::OptionThemeCondition::Some(azul_css::dynamic_selector::ThemeCondition::Dark),
227            ..Default::default()
228        }
229    }
230    
231    /// Create a mock for light theme
232    pub fn light_theme() -> Self {
233        Self {
234            theme: azul_css::dynamic_selector::OptionThemeCondition::Some(azul_css::dynamic_selector::ThemeCondition::Light),
235            ..Default::default()
236        }
237    }
238    
239    /// Apply this mock to a DynamicSelectorContext
240    pub fn apply_to(&self, ctx: &mut azul_css::dynamic_selector::DynamicSelectorContext) {
241        if let azul_css::dynamic_selector::OptionOsCondition::Some(os) = self.os {
242            ctx.os = os;
243        }
244        if let azul_css::dynamic_selector::OptionOsVersion::Some(os_version) = self.os_version {
245            ctx.os_version = os_version;
246        }
247        if let azul_css::dynamic_selector::OptionLinuxDesktopEnv::Some(de) = self.desktop_env {
248            ctx.desktop_env = azul_css::dynamic_selector::OptionLinuxDesktopEnv::Some(de);
249        }
250        if let azul_css::dynamic_selector::OptionThemeCondition::Some(ref theme) = self.theme {
251            ctx.theme = theme.clone();
252        }
253        if let azul_css::OptionString::Some(ref lang) = self.language {
254            ctx.language = lang.clone();
255        }
256        if let azul_css::OptionBool::Some(reduced) = self.prefers_reduced_motion {
257            ctx.prefers_reduced_motion = if reduced {
258                azul_css::dynamic_selector::BoolCondition::True
259            } else {
260                azul_css::dynamic_selector::BoolCondition::False
261            };
262        }
263        if let azul_css::OptionBool::Some(high_contrast) = self.prefers_high_contrast {
264            ctx.prefers_high_contrast = if high_contrast {
265                azul_css::dynamic_selector::BoolCondition::True
266            } else {
267                azul_css::dynamic_selector::BoolCondition::False
268            };
269        }
270        if let azul_css::OptionF32::Some(w) = self.viewport_width {
271            ctx.viewport_width = w;
272        }
273        if let azul_css::OptionF32::Some(h) = self.viewport_height {
274            ctx.viewport_height = h;
275        }
276    }
277}
278
279impl_option!(
280    CssMockEnvironment,
281    OptionCssMockEnvironment,
282    copy = false,
283    [Debug, Clone]
284);
285
286/// A route mapping a URL pattern to a layout callback.
287///
288/// Routes are cross-platform: on desktop, switching routes swaps the
289/// active layout callback and triggers `RefreshDom`. On web, it also
290/// calls `history.pushState()` for browser navigation.
291///
292/// # Pattern syntax
293///
294/// - `"/"` — exact root
295/// - `"/about"` — exact path
296/// - `"/user/:id"` — parameterized segment, `/user/42` yields `id = "42"`
297///
298/// # C API
299/// ```c
300/// AzAppConfig_addRoute(&config, AzString_fromConstStr("/user/:id"), layout_user);
301/// ```
302#[repr(C)]
303pub struct Route {
304    /// URL pattern (e.g. `"/"`, `"/about"`, `"/user/:id"`)
305    pub pattern: AzString,
306    /// Layout callback invoked when this route is active
307    pub layout_callback: LayoutCallback,
308}
309
310impl Clone for Route {
311    fn clone(&self) -> Self {
312        Self { pattern: self.pattern.clone(), layout_callback: self.layout_callback.clone() }
313    }
314}
315impl fmt::Debug for Route {
316    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
317        f.debug_struct("Route")
318            .field("pattern", &self.pattern)
319            .field("layout_callback", &self.layout_callback)
320            .finish()
321    }
322}
323impl PartialEq for Route { fn eq(&self, o: &Self) -> bool { self.pattern == o.pattern && self.layout_callback == o.layout_callback } }
324impl Eq for Route {}
325impl PartialOrd for Route { fn partial_cmp(&self, o: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(o)) } }
326impl Ord for Route { fn cmp(&self, o: &Self) -> core::cmp::Ordering { self.pattern.cmp(&o.pattern).then_with(|| self.layout_callback.cmp(&o.layout_callback)) } }
327impl Hash for Route { fn hash<H: Hasher>(&self, state: &mut H) { self.pattern.hash(state); self.layout_callback.hash(state); } }
328
329impl_option!(Route, OptionRoute, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
330impl_vec!(Route, RouteVec, RouteVecDestructor, RouteVecDestructorType, RouteVecSlice, OptionRoute);
331impl_vec_mut!(Route, RouteVec);
332impl_vec_debug!(Route, RouteVec);
333impl_vec_clone!(Route, RouteVec, RouteVecDestructor);
334impl_vec_partialeq!(Route, RouteVec);
335impl_vec_eq!(Route, RouteVec);
336impl_vec_partialord!(Route, RouteVec);
337impl_vec_ord!(Route, RouteVec);
338impl_vec_hash!(Route, RouteVec);
339
340/// Result of matching a URL against a route pattern.
341///
342/// Stores the matched pattern and any extracted parameters.
343/// Available to layout callbacks via `LayoutCallbackInfo::get_route_param()`.
344#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
345#[repr(C)]
346pub struct RouteMatch {
347    /// The matched route pattern (e.g. `"/user/:id"`)
348    pub pattern: AzString,
349    /// Extracted parameters (e.g. `[("id", "42")]`)
350    pub params: StringPairVec,
351}
352
353impl RouteMatch {
354    /// Get a route parameter by key.
355    pub fn get_param(&self, key: &str) -> Option<&AzString> {
356        self.params.get_key(key)
357    }
358}
359
360impl_option!(RouteMatch, OptionRouteMatch, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
361
362/// Match a URL path against a route pattern, extracting parameters.
363///
364/// Returns `Some(RouteMatch)` with extracted params on match, `None` otherwise.
365///
366/// # Examples
367/// - pattern `"/user/:id"`, path `"/user/42"` → `Some(RouteMatch { params: [("id","42")] })`
368/// - pattern `"/"`, path `"/"` → `Some(RouteMatch { params: [] })`
369/// - pattern `"/about"`, path `"/settings"` → `None`
370pub fn match_route(pattern: &str, path: &str) -> Option<RouteMatch> {
371    let pat_segs: Vec<&str> = pattern.split('/').filter(|s| !s.is_empty()).collect();
372    let path_segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
373
374    if pat_segs.len() != path_segs.len() {
375        return None;
376    }
377
378    let mut params = Vec::new();
379    for (pat, val) in pat_segs.iter().zip(path_segs.iter()) {
380        if let Some(param_name) = pat.strip_prefix(':') {
381            params.push(AzStringPair {
382                key: AzString::from(param_name.to_string()),
383                value: AzString::from(val.to_string()),
384            });
385        } else if pat != val {
386            return None;
387        }
388    }
389
390    Some(RouteMatch {
391        pattern: AzString::from(pattern.to_string()),
392        params: StringPairVec::from_vec(params),
393    })
394}
395
396/// Configuration for optional features, such as whether to enable logging or panic hooks
397#[derive(Debug, Clone)]
398#[repr(C)]
399pub struct AppConfig {
400    /// If enabled, logs error and info messages.
401    ///
402    /// Default is `LevelFilter::Error` to log all errors by default
403    pub log_level: AppLogLevel,
404    /// If the app crashes / panics, a window with a message box pops up.
405    /// Setting this to `false` disables the popup box.
406    pub enable_visual_panic_hook: bool,
407    /// If this is set to `true` (the default), a backtrace + error information
408    /// gets logged to stdout and the logging file (only if logging is enabled).
409    pub enable_logging_on_panic: bool,
410    /// (STUB) Whether keyboard navigation should be enabled (default: true).
411    /// Currently not implemented.
412    pub enable_tab_navigation: bool,
413    /// Determines what happens when all windows are closed.
414    /// Default: EndProcess (terminate when last window closes).
415    pub termination_behavior: AppTerminationBehavior,
416    /// Icon provider for the application.
417    /// Register icons here before calling App::run().
418    /// Each window will clone this provider (cheap, Arc-based).
419    pub icon_provider: crate::icon::IconProviderHandle,
420    /// Fonts bundled with the application.
421    /// These fonts are loaded into memory and take priority over system fonts.
422    pub bundled_fonts: NamedFontVec,
423    /// Configuration for how system fonts should be loaded.
424    /// Default: LoadAllSystemFonts (scan all system fonts at startup)
425    pub font_loading: FontLoadingConfig,
426    /// Optional mock environment for CSS evaluation.
427    /// 
428    /// When set, this overrides the auto-detected system properties (OS, theme, etc.)
429    /// for CSS @-rules and dynamic selectors. This is useful for:
430    /// - Testing OS-specific styles on a different platform
431    /// - Screenshot testing with consistent environment
432    /// - Previewing how the app looks on different systems
433    /// 
434    /// Default: None (use auto-detected system properties)
435    pub mock_css_environment: OptionCssMockEnvironment,
436    /// System style detected at startup (theme, colors, fonts, etc.)
437    /// 
438    /// This is detected once at `AppConfig::create()` and passed to all windows.
439    /// You can override this after creation to use a custom system style,
440    /// for example to test how your app looks on a different platform.
441    pub system_style: SystemStyle,
442    /// Component libraries registered at startup.
443    ///
444    /// Use `add_component()` to register individual components, or
445    /// `add_component_library()` to register entire libraries.
446    /// User-registered (and built-in) component libraries.
447    ///
448    /// The 52 built-in HTML elements are automatically registered by
449    /// `AppConfig::create()` via `register_builtin_components`.
450    /// Additional libraries can be added with `add_component_library`.
451    pub component_libraries: ComponentLibraryVec,
452    /// Registered routes mapping URL patterns to layout callbacks.
453    ///
454    /// Cross-platform: on desktop, the active route determines which layout
455    /// callback runs. On web, routes map to HTTP endpoints and browser URLs.
456    ///
457    /// The first route (or `"/"`) is the default. Use `add_route()` to register.
458    pub routes: RouteVec,
459}
460
461impl AppConfig {
462    pub fn create() -> Self {
463        let log_level = AppLogLevel::Error;
464        let icon_provider = crate::icon::IconProviderHandle::new();
465        let bundled_fonts = NamedFontVec::from_const_slice(&[]);
466        let font_loading = FontLoadingConfig::default();
467        let system_style = SystemStyle::detect();
468        let mut s = Self {
469            log_level,
470            enable_visual_panic_hook: false,
471            enable_logging_on_panic: true,
472            enable_tab_navigation: true,
473            termination_behavior: AppTerminationBehavior::default(),
474            icon_provider,
475            bundled_fonts,
476            font_loading,
477            mock_css_environment: OptionCssMockEnvironment::None,
478            system_style,
479            component_libraries: ComponentLibraryVec::from_const_slice(&[]),
480            routes: RouteVec::from_const_slice(&[]),
481        };
482        // Dogfood: register the 52 built-in HTML elements via the
483        // same `add_component_library` API that users call.
484        s.add_component_library(
485            AzString::from_const_str("builtin"),
486            crate::xml::register_builtin_components as extern "C" fn() -> crate::xml::ComponentLibrary,
487        );
488        s
489    }
490    
491    /// Create config with a mock CSS environment for testing
492    /// 
493    /// This allows you to simulate how your app would look on a different OS,
494    /// with a different theme, language, or accessibility settings.
495    /// 
496    /// # Example
497    /// ```rust
498    /// # use azul_core::resources::{AppConfig, CssMockEnvironment};
499    /// # use azul_css::dynamic_selector::{OsCondition, OptionOsCondition, ThemeCondition, OptionThemeCondition};
500    /// let config = AppConfig::create()
501    ///     .with_mock_environment(CssMockEnvironment {
502    ///         os: OptionOsCondition::Some(OsCondition::Linux),
503    ///         theme: OptionThemeCondition::Some(ThemeCondition::Dark),
504    ///         ..Default::default()
505    ///     });
506    /// ```
507    pub fn with_mock_environment(mut self, env: CssMockEnvironment) -> Self {
508        self.mock_css_environment = OptionCssMockEnvironment::Some(env);
509        self
510    }
511
512    /// Register a single component into a named library.
513    ///
514    /// Calls `register_fn` immediately and adds the returned `ComponentDef`
515    /// to the library named `library`. If no library with that name exists,
516    /// a new one is created. If a component with the same `id.name` already
517    /// exists in the library, it is replaced.
518    ///
519    /// # C API
520    /// ```c
521    /// AzAppConfig_addComponent(&config, AzString_fromConstStr("mylib"), my_register_fn);
522    /// ```
523    pub fn add_component<R: Into<RegisterComponentFn>>(&mut self, library: AzString, register_fn: R) {
524        let register_fn = register_fn.into();
525        let component = (register_fn.cb)();
526        let empty_libs = ComponentLibraryVec::from_const_slice(&[]);
527        let mut libs = core::mem::replace(&mut self.component_libraries, empty_libs).into_library_owned_vec();
528
529        if let Some(existing_lib) = libs.iter_mut().find(|l| l.name.as_str() == library.as_str()) {
530            let empty_comps = ComponentDefVec::from_const_slice(&[]);
531            let mut comps = core::mem::replace(&mut existing_lib.components, empty_comps).into_library_owned_vec();
532            if let Some(ec) = comps.iter_mut().find(|c| c.id.name.as_str() == component.id.name.as_str()) {
533                *ec = component;
534            } else {
535                comps.push(component);
536            }
537            existing_lib.components = ComponentDefVec::from_vec(comps);
538        } else {
539            libs.push(ComponentLibrary {
540                name: library,
541                version: AzString::from_const_str("1.0.0"),
542                description: AzString::from_const_str(""),
543                components: ComponentDefVec::from_vec(alloc::vec![component]),
544                exportable: true,
545                modifiable: true,
546                data_models: crate::xml::ComponentDataModelVec::from_const_slice(&[]),
547                enum_models: crate::xml::ComponentEnumModelVec::from_const_slice(&[]),
548            });
549        }
550
551        self.component_libraries = ComponentLibraryVec::from_vec(libs);
552    }
553
554    /// Register an entire component library.
555    ///
556    /// Calls `register_fn` immediately and adds the returned
557    /// `ComponentLibrary` to the config. Uses `name` as the library name
558    /// (overriding whatever the function sets). If a library with the same
559    /// name already exists, it is replaced wholesale.
560    ///
561    /// # C API
562    /// ```c
563    /// AzAppConfig_addComponentLibrary(&config, AzString_fromConstStr("vendor"), my_lib_fn);
564    /// ```
565    pub fn add_component_library<R: Into<RegisterComponentLibraryFn>>(&mut self, name: AzString, register_fn: R) {
566        let register_fn = register_fn.into();
567        let mut library = (register_fn.cb)();
568        library.name = name;
569
570        let empty_libs = ComponentLibraryVec::from_const_slice(&[]);
571        let mut libs = core::mem::replace(&mut self.component_libraries, empty_libs).into_library_owned_vec();
572        if let Some(existing) = libs.iter_mut().find(|l| l.name.as_str() == library.name.as_str()) {
573            *existing = library;
574        } else {
575            libs.push(library);
576        }
577
578        self.component_libraries = ComponentLibraryVec::from_vec(libs);
579    }
580
581    /// Register a route mapping a URL pattern to a layout callback.
582    ///
583    /// On web: each route becomes an HTTP endpoint. On desktop: the first
584    /// route (or `"/"`) is the initial layout, and `CallbackInfo::switch_route()`
585    /// swaps the active callback.
586    ///
587    /// # C API
588    /// ```c
589    /// AzAppConfig_addRoute(&config, AzString_fromConstStr("/user/:id"), layout_user);
590    /// ```
591    pub fn add_route<P: Into<AzString>, L: Into<LayoutCallback>>(&mut self, pattern: P, layout_fn: L) {
592        let route = Route {
593            pattern: pattern.into(),
594            layout_callback: layout_fn.into(),
595        };
596        let empty = RouteVec::from_const_slice(&[]);
597        let mut routes = core::mem::replace(&mut self.routes, empty).into_library_owned_vec();
598        // Replace existing route with the same pattern
599        if let Some(existing) = routes.iter_mut().find(|r| r.pattern.as_str() == route.pattern.as_str()) {
600            *existing = route;
601        } else {
602            routes.push(route);
603        }
604        self.routes = RouteVec::from_vec(routes);
605    }
606
607    /// Find the route matching a given URL path.
608    ///
609    /// Returns the matched `Route` and a `RouteMatch` with extracted parameters.
610    pub fn match_route_for_path(&self, path: &str) -> Option<(&Route, RouteMatch)> {
611        for route in self.routes.as_ref().iter() {
612            if let Some(m) = match_route(route.pattern.as_str(), path) {
613                return Some((route, m));
614            }
615        }
616        None
617    }
618}
619
620impl Default for AppConfig {
621    fn default() -> Self {
622        Self::create()
623    }
624}
625
626#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
627#[repr(C)]
628pub enum AppLogLevel {
629    Off,
630    Error,
631    Warn,
632    Info,
633    Debug,
634    Trace,
635}
636
637/// Metadata (but not storage) describing an image In WebRender.
638#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
639#[repr(C)]
640pub struct ImageDescriptor {
641    /// Format of the image data.
642    pub format: RawImageFormat,
643    /// Width and height of the image data, in pixels.
644    pub width: usize,
645    pub height: usize,
646    /// The number of bytes from the start of one row to the next. If non-None,
647    /// `compute_stride` will return this value, otherwise it returns
648    /// `width * bpp`. Different source of images have different alignment
649    /// constraints for rows, so the stride isn't always equal to width * bpp.
650    pub stride: OptionI32,
651    /// Offset in bytes of the first pixel of this image in its backing buffer.
652    /// This is used for tiling, wherein WebRender extracts chunks of input images
653    /// in order to cache, manipulate, and render them individually. This offset
654    /// tells the texture upload machinery where to find the bytes to upload for
655    /// this tile. Non-tiled images generally set this to zero.
656    pub offset: i32,
657    /// Various bool flags related to this descriptor.
658    pub flags: ImageDescriptorFlags,
659}
660
661/// Various flags that are part of an image descriptor.
662#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
663#[repr(C)]
664pub struct ImageDescriptorFlags {
665    /// Whether this image is opaque, or has an alpha channel. Avoiding blending
666    /// for opaque surfaces is an important optimization.
667    pub is_opaque: bool,
668    /// Whether to allow the driver to automatically generate mipmaps. If images
669    /// are already downscaled appropriately, mipmap generation can be wasted
670    /// work, and cause performance problems on some cards/drivers.
671    ///
672    /// See https://github.com/servo/webrender/pull/2555/
673    pub allow_mipmaps: bool,
674}
675
676#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
677pub struct IdNamespace(pub u32);
678
679impl ::core::fmt::Display for IdNamespace {
680    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
681        write!(f, "IdNamespace({})", self.0)
682    }
683}
684
685impl ::core::fmt::Debug for IdNamespace {
686    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
687        write!(f, "{}", self)
688    }
689}
690
691#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
692#[repr(C)]
693pub enum RawImageFormat {
694    R8,
695    RG8,
696    RGB8,
697    RGBA8,
698    R16,
699    RG16,
700    RGB16,
701    RGBA16,
702    BGR8,
703    BGRA8,
704    RGBF32,
705    RGBAF32,
706}
707
708// NOTE: starts at 1 (0 = DUMMY)
709static IMAGE_KEY: AtomicU64 = AtomicU64::new(1);
710static FONT_KEY: AtomicU64 = AtomicU64::new(0);
711static FONT_INSTANCE_KEY: AtomicU64 = AtomicU64::new(0);
712
713#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
714pub struct ImageKey {
715    pub namespace: IdNamespace,
716    pub key: u64,
717}
718
719impl ImageKey {
720    pub const DUMMY: Self = Self {
721        namespace: IdNamespace(0),
722        key: 0,
723    };
724
725    pub fn unique(render_api_namespace: IdNamespace) -> Self {
726        Self {
727            namespace: render_api_namespace,
728            key: IMAGE_KEY.fetch_add(1, AtomicOrdering::SeqCst),
729        }
730    }
731}
732
733#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
734pub struct FontKey {
735    pub namespace: IdNamespace,
736    pub key: u64,
737}
738
739impl FontKey {
740    pub fn unique(render_api_namespace: IdNamespace) -> Self {
741        Self {
742            namespace: render_api_namespace,
743            key: FONT_KEY.fetch_add(1, AtomicOrdering::SeqCst),
744        }
745    }
746}
747
748#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
749pub struct FontInstanceKey {
750    pub namespace: IdNamespace,
751    pub key: u64,
752}
753
754impl FontInstanceKey {
755    pub fn unique(render_api_namespace: IdNamespace) -> Self {
756        Self {
757            namespace: render_api_namespace,
758            key: FONT_INSTANCE_KEY.fetch_add(1, AtomicOrdering::SeqCst),
759        }
760    }
761}
762
763// NOTE: This type should NOT be exposed in the API!
764// The only public functions are the constructors
765#[derive(Debug)]
766pub enum DecodedImage {
767    /// Image that has a reserved key, but no data, i.e it is not yet rendered
768    /// or there was an error during rendering
769    NullImage {
770        width: usize,
771        height: usize,
772        format: RawImageFormat,
773        /// Sometimes images need to be tagged with extra data
774        tag: Vec<u8>,
775    },
776    // OpenGl texture
777    Gl(Texture),
778    // Image backed by CPU-rendered pixels
779    Raw((ImageDescriptor, ImageData)),
780    // Same as `Texture`, but rendered AFTER the layout has been done
781    Callback(CoreImageCallback),
782    // YUVImage(...)
783    // VulkanSurface(...)
784    // MetalSurface(...),
785    // DirectXSurface(...)
786}
787
788#[derive(Debug)]
789#[repr(C)]
790pub struct ImageRef {
791    /// Shared pointer to an opaque implementation of the decoded image
792    pub data: *const DecodedImage,
793    /// How many copies does this image have (if 0, the font data will be deleted on drop)
794    pub copies: *const AtomicUsize,
795    pub run_destructor: bool,
796}
797
798impl ImageRef {
799    pub fn get_hash(&self) -> ImageRefHash {
800        image_ref_get_hash(self)
801    }
802}
803
804#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Ord, Eq)]
805#[repr(C)]
806pub struct ImageRefHash {
807    pub inner: usize,
808}
809
810impl_option!(
811    ImageRef,
812    OptionImageRef,
813    copy = false,
814    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
815);
816
817impl ImageRef {
818    /// If *copies = 1, returns the internal image data
819    pub fn into_inner(self) -> Option<DecodedImage> {
820        unsafe {
821            if self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) == Some(1) {
822                let data = Box::from_raw(self.data as *mut DecodedImage);
823                let _ = Box::from_raw(self.copies as *mut AtomicUsize);
824                core::mem::forget(self); // do not run the destructor
825                Some(*data)
826            } else {
827                None
828            }
829        }
830    }
831
832    pub fn get_data<'a>(&'a self) -> &'a DecodedImage {
833        unsafe { &*self.data }
834    }
835
836    pub fn get_image_callback<'a>(&'a self) -> Option<&'a CoreImageCallback> {
837        if unsafe { self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) != Some(1) } {
838            return None; // not safe
839        }
840
841        match unsafe { &*self.data } {
842            DecodedImage::Callback(gl_texture_callback) => Some(gl_texture_callback),
843            _ => None,
844        }
845    }
846
847    pub fn get_image_callback_mut<'a>(&'a mut self) -> Option<&'a mut CoreImageCallback> {
848        if unsafe { self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) != Some(1) } {
849            return None; // not safe
850        }
851
852        match unsafe { &mut *(self.data as *mut DecodedImage) } {
853            DecodedImage::Callback(gl_texture_callback) => Some(gl_texture_callback),
854            _ => None,
855        }
856    }
857
858    /// In difference to the default shallow copy, creates a new image ref
859    pub fn deep_copy(&self) -> Self {
860        let new_data = match self.get_data() {
861            DecodedImage::NullImage {
862                width,
863                height,
864                format,
865                tag,
866            } => DecodedImage::NullImage {
867                width: *width,
868                height: *height,
869                format: *format,
870                tag: tag.clone(),
871            },
872            // NOTE: textures cannot be deep-copied yet (since the OpenGL calls for that
873            // are missing from the trait), so calling clone() on a GL texture will result in an
874            // empty image
875            DecodedImage::Gl(tex) => DecodedImage::NullImage {
876                width: tex.size.width as usize,
877                height: tex.size.height as usize,
878                format: tex.format,
879                tag: Vec::new(),
880            },
881            // WARNING: the data may still be a U8Vec<'static> - the data may still not be
882            // actually cloned. The data only gets cloned on a write operation
883            DecodedImage::Raw((descriptor, data)) => {
884                DecodedImage::Raw((descriptor.clone(), data.clone()))
885            }
886            DecodedImage::Callback(cb) => DecodedImage::Callback(cb.clone()),
887        };
888
889        Self::new(new_data)
890    }
891
892    pub fn is_null_image(&self) -> bool {
893        match self.get_data() {
894            DecodedImage::NullImage { .. } => true,
895            _ => false,
896        }
897    }
898
899    pub fn is_gl_texture(&self) -> bool {
900        match self.get_data() {
901            DecodedImage::Gl(_) => true,
902            _ => false,
903        }
904    }
905
906    pub fn is_raw_image(&self) -> bool {
907        match self.get_data() {
908            DecodedImage::Raw((_, _)) => true,
909            _ => false,
910        }
911    }
912
913    pub fn is_callback(&self) -> bool {
914        match self.get_data() {
915            DecodedImage::Callback(_) => true,
916            _ => false,
917        }
918    }
919
920    // OptionRawImage
921    pub fn get_rawimage(&self) -> Option<RawImage> {
922        match self.get_data() {
923            DecodedImage::Raw((image_descriptor, image_data)) => Some(RawImage {
924                pixels: match image_data {
925                    ImageData::Raw(shared_data) => {
926                        // Clone the SharedRawImageData (increments ref count),
927                        // then try to extract or convert to U8Vec
928                        let data_clone = shared_data.clone();
929                        if let Some(u8vec) = data_clone.into_inner() {
930                            RawImageData::U8(u8vec)
931                        } else {
932                            // Multiple references exist, need to copy the data
933                            RawImageData::U8(shared_data.as_ref().to_vec().into())
934                        }
935                    }
936                    ImageData::External(_) => return None,
937                },
938                width: image_descriptor.width,
939                height: image_descriptor.height,
940                premultiplied_alpha: true,
941                data_format: image_descriptor.format,
942                tag: Vec::new().into(),
943            }),
944            _ => None,
945        }
946    }
947
948    /// Get raw bytes from the image as a slice
949    /// Returns None if this is not a Raw image or if it's an External image
950    pub fn get_bytes(&self) -> Option<&[u8]> {
951        match self.get_data() {
952            DecodedImage::Raw((_, image_data)) => match image_data {
953                ImageData::Raw(shared_data) => Some(shared_data.as_ref()),
954                ImageData::External(_) => None,
955            },
956            _ => None,
957        }
958    }
959
960    /// Get a pointer to the raw bytes for debugging/profiling purposes
961    /// Returns a unique pointer for this ImageRef's data
962    pub fn get_bytes_ptr(&self) -> *const u8 {
963        match self.get_data() {
964            DecodedImage::Raw((_, image_data)) => match image_data {
965                ImageData::Raw(shared_data) => shared_data.as_ptr(),
966                ImageData::External(_) => core::ptr::null(),
967            },
968            _ => core::ptr::null(),
969        }
970    }
971
972    /// NOTE: returns (0, 0) for a Callback
973    pub fn get_size(&self) -> LogicalSize {
974        match self.get_data() {
975            DecodedImage::NullImage { width, height, .. } => {
976                LogicalSize::new(*width as f32, *height as f32)
977            }
978            DecodedImage::Gl(tex) => {
979                LogicalSize::new(tex.size.width as f32, tex.size.height as f32)
980            }
981            DecodedImage::Raw((image_descriptor, _)) => LogicalSize::new(
982                image_descriptor.width as f32,
983                image_descriptor.height as f32,
984            ),
985            DecodedImage::Callback(_) => LogicalSize::new(0.0, 0.0),
986        }
987    }
988
989    pub fn null_image(width: usize, height: usize, format: RawImageFormat, tag: Vec<u8>) -> Self {
990        Self::new(DecodedImage::NullImage {
991            width,
992            height,
993            format,
994            tag,
995        })
996    }
997
998    pub fn callback<C: Into<CoreRenderImageCallback>>(callback: C, data: RefAny) -> Self {
999        Self::new(DecodedImage::Callback(CoreImageCallback {
1000            callback: callback.into(),
1001            refany: data,
1002        }))
1003    }
1004
1005    pub fn new_rawimage(image_data: RawImage) -> Option<Self> {
1006        let (image_data, image_descriptor) = image_data.into_loaded_image_source()?;
1007        Some(Self::new(DecodedImage::Raw((image_descriptor, image_data))))
1008    }
1009
1010    pub fn new_gltexture(texture: Texture) -> Self {
1011        Self::new(DecodedImage::Gl(texture))
1012    }
1013
1014    fn new(data: DecodedImage) -> Self {
1015        Self {
1016            data: Box::into_raw(Box::new(data)),
1017            copies: Box::into_raw(Box::new(AtomicUsize::new(1))),
1018            run_destructor: true,
1019        }
1020    }
1021
1022    // pub fn new_vulkan(...) -> Self
1023}
1024
1025unsafe impl Send for ImageRef {}
1026unsafe impl Sync for ImageRef {}
1027
1028impl PartialEq for ImageRef {
1029    fn eq(&self, rhs: &Self) -> bool {
1030        self.data as usize == rhs.data as usize
1031    }
1032}
1033
1034impl PartialOrd for ImageRef {
1035    fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
1036        Some((self.data as usize).cmp(&(other.data as usize)))
1037    }
1038}
1039
1040impl Ord for ImageRef {
1041    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1042        let self_data = self.data as usize;
1043        let other_data = other.data as usize;
1044        self_data.cmp(&other_data)
1045    }
1046}
1047
1048impl Eq for ImageRef {}
1049
1050impl Hash for ImageRef {
1051    fn hash<H>(&self, state: &mut H)
1052    where
1053        H: Hasher,
1054    {
1055        let self_data = self.data as usize;
1056        self_data.hash(state)
1057    }
1058}
1059
1060impl Clone for ImageRef {
1061    fn clone(&self) -> Self {
1062        unsafe {
1063            self.copies
1064                .as_ref()
1065                .map(|m| m.fetch_add(1, AtomicOrdering::SeqCst));
1066        }
1067        Self {
1068            data: self.data,     // copy the pointer
1069            copies: self.copies, // copy the pointer
1070            run_destructor: true,
1071        }
1072    }
1073}
1074
1075impl Drop for ImageRef {
1076    fn drop(&mut self) {
1077        self.run_destructor = false;
1078        unsafe {
1079            let copies = (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst);
1080            if copies == 1 {
1081                let _ = Box::from_raw(self.data as *mut DecodedImage);
1082                let _ = Box::from_raw(self.copies as *mut AtomicUsize);
1083            }
1084        }
1085    }
1086}
1087
1088pub fn image_ref_get_hash(ir: &ImageRef) -> ImageRefHash {
1089    ImageRefHash {
1090        inner: ir.data as usize,
1091    }
1092}
1093
1094/// Convert a stable ImageRefHash directly to an ImageKey.
1095///
1096/// `ImageKey.key` is a `u64`, so the pointer-derived `ImageRefHash.inner: usize`
1097/// round-trips losslessly on both 32- and 64-bit platforms without any folding.
1098pub fn image_ref_hash_to_image_key(hash: ImageRefHash, namespace: IdNamespace) -> ImageKey {
1099    ImageKey {
1100        namespace,
1101        key: hash.inner as u64,
1102    }
1103}
1104
1105pub fn font_ref_get_hash(fr: &FontRef) -> u64 {
1106    fr.get_hash()
1107}
1108
1109/// Stores the resources for the application, such as fonts, images and cached
1110/// texts, also clipboard strings
1111///
1112/// Images and fonts can be references across window contexts (not yet tested,
1113/// but should work).
1114#[derive(Debug)]
1115pub struct ImageCache {
1116    /// The AzString is the string used in the CSS, i.e. url("my_image") = "my_image" -> ImageId(4)
1117    ///
1118    /// NOTE: This is the only map that is modifiable by the user and that has to be manually
1119    /// managed all other maps are library-internal only and automatically delete their
1120    /// resources once they aren't needed anymore
1121    pub image_id_map: OrderedMap<AzString, ImageRef>,
1122}
1123
1124impl Default for ImageCache {
1125    fn default() -> Self {
1126        Self {
1127            image_id_map: OrderedMap::default(),
1128        }
1129    }
1130}
1131
1132impl ImageCache {
1133    pub fn new() -> Self {
1134        Self::default()
1135    }
1136
1137    // -- ImageId cache
1138
1139    pub fn add_css_image_id(&mut self, css_id: AzString, image: ImageRef) {
1140        self.image_id_map.insert(css_id, image);
1141    }
1142
1143    pub fn get_css_image_id(&self, css_id: &AzString) -> Option<&ImageRef> {
1144        self.image_id_map.get(css_id)
1145    }
1146
1147    pub fn delete_css_image_id(&mut self, css_id: &AzString) {
1148        self.image_id_map.remove(css_id);
1149    }
1150}
1151
1152#[derive(Debug, Copy, Clone, PartialEq)]
1153pub struct ResolvedImage {
1154    pub key: ImageKey,
1155    pub descriptor: ImageDescriptor,
1156}
1157
1158/// Trait for accessing font resources
1159pub trait RendererResourcesTrait: core::fmt::Debug {
1160    /// Get a font family hash from a font families hash
1161    fn get_font_family(
1162        &self,
1163        style_font_families_hash: &StyleFontFamiliesHash,
1164    ) -> Option<&StyleFontFamilyHash>;
1165
1166    /// Get a font key from a font family hash
1167    fn get_font_key(&self, style_font_family_hash: &StyleFontFamilyHash) -> Option<&FontKey>;
1168
1169    /// Get a registered font and its instances from a font key
1170    fn get_registered_font(
1171        &self,
1172        font_key: &FontKey,
1173    ) -> Option<&(FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)>;
1174
1175    /// Get image information from an image hash
1176    fn get_image(&self, hash: &ImageRefHash) -> Option<&ResolvedImage>;
1177
1178    /// Update an image descriptor for an existing image hash
1179    fn update_image(
1180        &mut self,
1181        image_ref_hash: &ImageRefHash,
1182        descriptor: crate::resources::ImageDescriptor,
1183    );
1184}
1185
1186// Implementation for the original RendererResources struct
1187impl RendererResourcesTrait for RendererResources {
1188    fn get_font_family(
1189        &self,
1190        style_font_families_hash: &StyleFontFamiliesHash,
1191    ) -> Option<&StyleFontFamilyHash> {
1192        self.font_families_map.get(style_font_families_hash)
1193    }
1194
1195    fn get_font_key(&self, style_font_family_hash: &StyleFontFamilyHash) -> Option<&FontKey> {
1196        self.font_id_map.get(style_font_family_hash)
1197    }
1198
1199    fn get_registered_font(
1200        &self,
1201        font_key: &FontKey,
1202    ) -> Option<&(FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)> {
1203        self.currently_registered_fonts.get(font_key)
1204    }
1205
1206    fn get_image(&self, hash: &ImageRefHash) -> Option<&ResolvedImage> {
1207        self.currently_registered_images.get(hash)
1208    }
1209
1210    fn update_image(
1211        &mut self,
1212        image_ref_hash: &ImageRefHash,
1213        descriptor: crate::resources::ImageDescriptor,
1214    ) {
1215        if let Some(s) = self.currently_registered_images.get_mut(image_ref_hash) {
1216            s.descriptor = descriptor;
1217        }
1218    }
1219}
1220
1221/// Renderer resources that manage font, image and font instance keys.
1222/// RendererResources are local to each renderer / window, since the
1223/// keys are not shared across renderers
1224///
1225/// The resources are automatically managed, meaning that they each new frame
1226/// (signified by start_frame_gc and end_frame_gc)
1227pub struct RendererResources {
1228    /// All image keys currently active in the RenderApi
1229    pub currently_registered_images: OrderedMap<ImageRefHash, ResolvedImage>,
1230    /// Reverse lookup: ImageKey -> ImageRefHash for display list translation
1231    pub image_key_map: OrderedMap<ImageKey, ImageRefHash>,
1232    /// All font keys currently active in the RenderApi
1233    pub currently_registered_fonts:
1234        OrderedMap<FontKey, (FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)>,
1235    /// Fonts registered on the last frame
1236    ///
1237    /// Fonts differ from images in that regard that we can't immediately
1238    /// delete them on a new frame, instead we have to delete them on "current frame + 1"
1239    /// This is because when the frame is being built, we do not know
1240    /// whether the font will actually be successfully loaded
1241    pub last_frame_registered_fonts:
1242        OrderedMap<FontKey, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>>,
1243    /// Map from the calculated families vec (["Arial", "Helvetica"])
1244    /// to the final loaded font that could be loaded
1245    /// (in this case "Arial" on Windows and "Helvetica" on Mac,
1246    /// because the fonts are loaded in fallback-order)
1247    pub font_families_map: OrderedMap<StyleFontFamiliesHash, StyleFontFamilyHash>,
1248    /// Same as AzString -> ImageId, but for fonts, i.e. "Roboto" -> FontId(9)
1249    pub font_id_map: OrderedMap<StyleFontFamilyHash, FontKey>,
1250    /// Direct mapping from font hash (from FontRef) to FontKey
1251    /// TODO: This should become part of SharedFontRegistry
1252    pub font_hash_map: OrderedMap<u64, FontKey>,
1253}
1254
1255impl fmt::Debug for RendererResources {
1256    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1257        write!(
1258            f,
1259            "RendererResources {{
1260                currently_registered_images: {:#?},
1261                currently_registered_fonts: {:#?},
1262                font_families_map: {:#?},
1263                font_id_map: {:#?},
1264            }}",
1265            self.currently_registered_images.keys().collect::<Vec<_>>(),
1266            self.currently_registered_fonts.keys().collect::<Vec<_>>(),
1267            self.font_families_map.keys().collect::<Vec<_>>(),
1268            self.font_id_map.keys().collect::<Vec<_>>(),
1269        )
1270    }
1271}
1272
1273impl Default for RendererResources {
1274    fn default() -> Self {
1275        Self {
1276            currently_registered_images: OrderedMap::default(),
1277            image_key_map: OrderedMap::default(),
1278            currently_registered_fonts: OrderedMap::default(),
1279            last_frame_registered_fonts: OrderedMap::default(),
1280            font_families_map: OrderedMap::default(),
1281            font_id_map: OrderedMap::default(),
1282            font_hash_map: OrderedMap::default(),
1283        }
1284    }
1285}
1286
1287impl RendererResources {
1288    pub fn get_renderable_font_data(
1289        &self,
1290        font_instance_key: &FontInstanceKey,
1291    ) -> Option<(&FontRef, Au, DpiScaleFactor)> {
1292        self.currently_registered_fonts
1293            .iter()
1294            .find_map(|(font_key, (font_ref, instances))| {
1295                instances.iter().find_map(|((au, dpi), instance_key)| {
1296                    if *instance_key == *font_instance_key {
1297                        Some((font_ref, *au, *dpi))
1298                    } else {
1299                        None
1300                    }
1301                })
1302            })
1303    }
1304
1305    pub fn get_font_instance_key_for_text(
1306        &self,
1307        font_size_px: f32,
1308        css_property_cache: &CssPropertyCache,
1309        node_data: &NodeData,
1310        node_id: &NodeId,
1311        styled_node_state: &StyledNodeState,
1312        dpi_scale: f32,
1313    ) -> Option<FontInstanceKey> {
1314        // Convert font size to StyleFontSize
1315        let font_size = StyleFontSize {
1316            inner: azul_css::props::basic::PixelValue::const_px(font_size_px as isize),
1317        };
1318
1319        // Convert to application units
1320        let font_size_au = font_size_to_au(font_size);
1321
1322        // Create DPI scale factor
1323        let dpi_scale_factor = DpiScaleFactor {
1324            inner: FloatValue::new(dpi_scale),
1325        };
1326
1327        // Get font family
1328        let font_family =
1329            css_property_cache.get_font_id_or_default(node_data, node_id, styled_node_state);
1330
1331        // Calculate hash and lookup font instance key
1332        let font_families_hash = StyleFontFamiliesHash::new(font_family.as_ref());
1333
1334        self.get_font_instance_key(&font_families_hash, font_size_au, dpi_scale_factor)
1335    }
1336
1337    pub fn get_font_instance_key(
1338        &self,
1339        font_families_hash: &StyleFontFamiliesHash,
1340        font_size_au: Au,
1341        dpi_scale: DpiScaleFactor,
1342    ) -> Option<FontInstanceKey> {
1343        let font_family_hash = self.get_font_family(font_families_hash)?;
1344        let font_key = self.get_font_key(font_family_hash)?;
1345        let (_, instances) = self.get_registered_font(font_key)?;
1346        instances.get(&(font_size_au, dpi_scale)).copied()
1347    }
1348
1349    // Delete all font family hashes that do not have a font key anymore
1350    fn remove_font_families_with_zero_references(&mut self) {
1351        let font_family_to_delete = self
1352            .font_id_map
1353            .iter()
1354            .filter_map(|(font_family, font_key)| {
1355                if !self.currently_registered_fonts.contains_key(font_key) {
1356                    Some(font_family.clone())
1357                } else {
1358                    None
1359                }
1360            })
1361            .collect::<Vec<_>>();
1362
1363        for f in font_family_to_delete {
1364            self.font_id_map.remove(&f); // font key does not exist anymore
1365        }
1366
1367        let font_families_to_delete = self
1368            .font_families_map
1369            .iter()
1370            .filter_map(|(font_families, font_family)| {
1371                if !self.font_id_map.contains_key(font_family) {
1372                    Some(font_families.clone())
1373                } else {
1374                    None
1375                }
1376            })
1377            .collect::<Vec<_>>();
1378
1379        for f in font_families_to_delete {
1380            self.font_families_map.remove(&f); // font family does not exist anymore
1381        }
1382    }
1383}
1384
1385// Result returned from rerender_image_callback() - should be used as:
1386//
1387// ```rust
1388// txn.update_image(
1389//     wr_translate_image_key(key),
1390//     wr_translate_image_descriptor(descriptor),
1391//     wr_translate_image_data(data),
1392//     &WrImageDirtyRect::All,
1393// );
1394// ```
1395#[derive(Debug, Clone)]
1396pub struct UpdateImageResult {
1397    pub key_to_update: ImageKey,
1398    pub new_descriptor: ImageDescriptor,
1399    pub new_image_data: ImageData,
1400}
1401
1402#[derive(Debug, Default)]
1403pub struct GlTextureCache {
1404    pub solved_textures:
1405        BTreeMap<DomId, BTreeMap<NodeId, (ImageKey, ImageDescriptor, ExternalImageId)>>,
1406    pub hashes: BTreeMap<(DomId, NodeId, ImageRefHash), ImageRefHash>,
1407}
1408
1409// necessary so the display list can be built in parallel
1410unsafe impl Send for GlTextureCache {}
1411
1412impl GlTextureCache {
1413    /// Initializes an empty cache
1414    pub fn empty() -> Self {
1415        Self {
1416            solved_textures: BTreeMap::new(),
1417            hashes: BTreeMap::new(),
1418        }
1419    }
1420
1421    /// Updates a given texture
1422    ///
1423    /// This is called when a texture needs to be re-rendered (e.g., on resize or animation frame).
1424    /// It updates the texture in the WebRender external image cache and updates the internal
1425    /// descriptor to reflect the new size.
1426    ///
1427    /// # Arguments
1428    ///
1429    /// * `dom_id` - The DOM ID containing the texture
1430    /// * `node_id` - The node ID of the image element
1431    /// * `document_id` - The WebRender document ID
1432    /// * `epoch` - The current frame epoch
1433    /// * `new_texture` - The new texture to use
1434    /// * `insert_into_active_gl_textures_fn` - Function to insert the texture into the cache
1435    ///
1436    /// # Returns
1437    ///
1438    /// The ExternalImageId if successful, None if the texture wasn't found in the cache
1439    pub fn update_texture(
1440        &mut self,
1441        dom_id: DomId,
1442        node_id: NodeId,
1443        document_id: DocumentId,
1444        epoch: Epoch,
1445        new_texture: Texture,
1446        insert_into_active_gl_textures_fn: &GlStoreImageFn,
1447    ) -> Option<ExternalImageId> {
1448        let new_descriptor = new_texture.get_descriptor();
1449        let di_map = self.solved_textures.get_mut(&dom_id)?;
1450        let entry = di_map.get_mut(&node_id)?;
1451
1452        // Update the descriptor
1453        entry.1 = new_descriptor;
1454
1455        // The ExternalImageId is deterministic from (dom_id, node_id), so the cache
1456        // entry can keep referencing the same id across re-renders.
1457        let external_image_id = texture_external_image_id(dom_id, node_id);
1458        (insert_into_active_gl_textures_fn)(document_id, epoch, new_texture, external_image_id);
1459        entry.2 = external_image_id;
1460
1461        Some(external_image_id)
1462    }
1463}
1464
1465macro_rules! unique_id {
1466    ($struct_name:ident, $counter_name:ident) => {
1467        #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
1468        #[repr(C)]
1469        pub struct $struct_name {
1470            pub id: usize,
1471        }
1472
1473        impl $struct_name {
1474            pub fn unique() -> Self {
1475                Self {
1476                    id: $counter_name.fetch_add(1, AtomicOrdering::SeqCst),
1477                }
1478            }
1479        }
1480    };
1481}
1482
1483// NOTE: the property key is unique across transform, color and opacity properties
1484static PROPERTY_KEY_COUNTER: AtomicUsize = AtomicUsize::new(0);
1485unique_id!(TransformKey, PROPERTY_KEY_COUNTER);
1486unique_id!(ColorKey, PROPERTY_KEY_COUNTER);
1487unique_id!(OpacityKey, PROPERTY_KEY_COUNTER);
1488
1489static IMAGE_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
1490unique_id!(ImageId, IMAGE_ID_COUNTER);
1491static FONT_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
1492unique_id!(FontId, FONT_ID_COUNTER);
1493
1494#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1495#[repr(C)]
1496pub struct ImageMask {
1497    pub image: ImageRef,
1498    pub rect: LogicalRect,
1499    pub repeat: bool,
1500}
1501
1502impl_option!(
1503    ImageMask,
1504    OptionImageMask,
1505    copy = false,
1506    [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
1507);
1508
1509#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1510pub enum ImmediateFontId {
1511    Resolved((StyleFontFamilyHash, FontKey)),
1512    Unresolved(StyleFontFamilyVec),
1513}
1514
1515#[derive(Debug, Clone, PartialEq, PartialOrd)]
1516#[repr(C, u8)]
1517pub enum RawImageData {
1518    // 8-bit image data
1519    U8(U8Vec),
1520    // 16-bit image data
1521    U16(U16Vec),
1522    // HDR image data
1523    F32(F32Vec),
1524}
1525
1526impl RawImageData {
1527    pub fn get_u8_vec_ref(&self) -> Option<&U8Vec> {
1528        match self {
1529            RawImageData::U8(v) => Some(v),
1530            _ => None,
1531        }
1532    }
1533
1534    pub fn get_u16_vec_ref(&self) -> Option<&U16Vec> {
1535        match self {
1536            RawImageData::U16(v) => Some(v),
1537            _ => None,
1538        }
1539    }
1540
1541    pub fn get_f32_vec_ref(&self) -> Option<&F32Vec> {
1542        match self {
1543            RawImageData::F32(v) => Some(v),
1544            _ => None,
1545        }
1546    }
1547
1548    fn get_u8_vec(self) -> Option<U8Vec> {
1549        match self {
1550            RawImageData::U8(v) => Some(v),
1551            _ => None,
1552        }
1553    }
1554
1555    fn get_u16_vec(self) -> Option<U16Vec> {
1556        match self {
1557            RawImageData::U16(v) => Some(v),
1558            _ => None,
1559        }
1560    }
1561}
1562
1563#[derive(Debug, Clone, PartialEq, PartialOrd)]
1564#[repr(C)]
1565pub struct RawImage {
1566    pub pixels: RawImageData,
1567    pub width: usize,
1568    pub height: usize,
1569    pub premultiplied_alpha: bool,
1570    pub data_format: RawImageFormat,
1571    pub tag: U8Vec,
1572}
1573
1574impl RawImage {
1575    /// Returns a null / empty image
1576    pub fn null_image() -> Self {
1577        Self {
1578            pixels: RawImageData::U8(Vec::new().into()),
1579            width: 0,
1580            height: 0,
1581            premultiplied_alpha: true,
1582            data_format: RawImageFormat::BGRA8,
1583            tag: Vec::new().into(),
1584        }
1585    }
1586
1587    /// Allocates a width * height, single-channel mask, used for drawing CPU image masks
1588    pub fn allocate_mask(size: LayoutSize) -> Self {
1589        Self {
1590            pixels: RawImageData::U8(
1591                vec![0; size.width.max(0) as usize * size.height.max(0) as usize].into(),
1592            ),
1593            width: size.width as usize,
1594            height: size.height as usize,
1595            premultiplied_alpha: true,
1596            data_format: RawImageFormat::R8,
1597            tag: Vec::new().into(),
1598        }
1599    }
1600
1601    /// Encodes a RawImage as BGRA8 bytes and premultiplies it if the alpha is not premultiplied
1602    ///
1603    /// Returns None if the width * height * BPP does not match
1604    ///
1605    /// TODO: autovectorization fails spectacularly, need to manually optimize!
1606    pub fn into_loaded_image_source(self) -> Option<(ImageData, ImageDescriptor)> {
1607        // From webrender/wrench
1608        // These are slow. Gecko's gfx/2d/Swizzle.cpp has better versions
1609        #[inline(always)]
1610        fn premultiply_alpha(array: &mut [u8]) {
1611            if array.len() != 4 {
1612                return;
1613            }
1614            let a = u32::from(array[3]);
1615            array[0] = (((array[0] as u32 * a) + 128) / 255) as u8;
1616            array[1] = (((array[1] as u32 * a) + 128) / 255) as u8;
1617            array[2] = (((array[2] as u32 * a) + 128) / 255) as u8;
1618        }
1619
1620        #[inline(always)]
1621        fn normalize_u16(i: u16) -> u8 {
1622            ((core::u16::MAX as f32 / i as f32) * core::u8::MAX as f32) as u8
1623        }
1624
1625        let RawImage {
1626            width,
1627            height,
1628            pixels,
1629            mut data_format,
1630            premultiplied_alpha,
1631            tag,
1632        } = self;
1633
1634        const FOUR_BPP: usize = 4;
1635        const TWO_CHANNELS: usize = 2;
1636        const THREE_CHANNELS: usize = 3;
1637        const FOUR_CHANNELS: usize = 4;
1638
1639        let mut is_opaque = true;
1640
1641        let expected_len = width * height;
1642
1643        let bytes: U8Vec = match data_format {
1644            RawImageFormat::R8 => {
1645                // Keep R8 data as-is — WebRender supports R8 natively.
1646                // This is important for image mask clips which need the
1647                // single-channel data (white=visible, black=clipped).
1648                let pixels = pixels.get_u8_vec()?;
1649
1650                if pixels.len() != expected_len {
1651                    return None;
1652                }
1653
1654                is_opaque = false;
1655                pixels
1656            }
1657            RawImageFormat::RG8 => {
1658                let pixels = pixels.get_u8_vec()?;
1659
1660                if pixels.len() != expected_len * TWO_CHANNELS {
1661                    return None;
1662                }
1663
1664                let mut px = vec![0; expected_len * FOUR_BPP];
1665
1666                // TODO: check that this function is SIMD optimized
1667                for (pixel_index, greyalpha) in
1668                    pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate()
1669                {
1670                    let grey = greyalpha[0];
1671                    let alpha = greyalpha[1];
1672
1673                    if alpha != 255 {
1674                        is_opaque = false;
1675                    }
1676
1677                    px[pixel_index * FOUR_BPP] = grey;
1678                    px[(pixel_index * FOUR_BPP) + 1] = grey;
1679                    px[(pixel_index * FOUR_BPP) + 2] = grey;
1680                    px[(pixel_index * FOUR_BPP) + 3] = alpha;
1681
1682                    if !premultiplied_alpha {
1683                        premultiply_alpha(
1684                            &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
1685                        );
1686                    }
1687                }
1688
1689                data_format = RawImageFormat::BGRA8;
1690                px.into()
1691            }
1692            RawImageFormat::RGB8 => {
1693                let pixels = pixels.get_u8_vec()?;
1694
1695                if pixels.len() != expected_len * THREE_CHANNELS {
1696                    return None;
1697                }
1698
1699                let mut px = vec![0; expected_len * FOUR_BPP];
1700
1701                // TODO: check that this function is SIMD optimized
1702                for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
1703                    let red = rgb[0];
1704                    let green = rgb[1];
1705                    let blue = rgb[2];
1706
1707                    px[pixel_index * FOUR_BPP] = blue;
1708                    px[(pixel_index * FOUR_BPP) + 1] = green;
1709                    px[(pixel_index * FOUR_BPP) + 2] = red;
1710                    px[(pixel_index * FOUR_BPP) + 3] = 0xff;
1711                }
1712
1713                data_format = RawImageFormat::BGRA8;
1714                px.into()
1715            }
1716            RawImageFormat::RGBA8 => {
1717                let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
1718
1719                if pixels.len() != expected_len * FOUR_CHANNELS {
1720                    return None;
1721                }
1722
1723                // TODO: check that this function is SIMD optimized
1724                // no extra allocation necessary, but swizzling
1725                if premultiplied_alpha {
1726                    for rgba in pixels.chunks_exact_mut(4) {
1727                        let (r, gba) = rgba.split_first_mut()?;
1728                        core::mem::swap(r, gba.get_mut(1)?);
1729                        let a = rgba.get_mut(3)?;
1730                        if *a != 255 {
1731                            is_opaque = false;
1732                        }
1733                    }
1734                } else {
1735                    for rgba in pixels.chunks_exact_mut(4) {
1736                        // RGBA => BGRA
1737                        let (r, gba) = rgba.split_first_mut()?;
1738                        core::mem::swap(r, gba.get_mut(1)?);
1739                        let a = rgba.get_mut(3)?;
1740                        if *a != 255 {
1741                            is_opaque = false;
1742                        }
1743                        premultiply_alpha(rgba); // <-
1744                    }
1745                }
1746
1747                data_format = RawImageFormat::BGRA8;
1748                pixels.into()
1749            }
1750            RawImageFormat::R16 => {
1751                let pixels = pixels.get_u16_vec()?;
1752
1753                if pixels.len() != expected_len {
1754                    return None;
1755                }
1756
1757                let mut px = vec![0; expected_len * FOUR_BPP];
1758
1759                // TODO: check that this function is SIMD optimized
1760                for (pixel_index, grey_u16) in pixels.as_ref().iter().enumerate() {
1761                    let grey_u8 = normalize_u16(*grey_u16);
1762                    px[pixel_index * FOUR_BPP] = grey_u8;
1763                    px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
1764                    px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
1765                    px[(pixel_index * FOUR_BPP) + 3] = 0xff;
1766                }
1767
1768                data_format = RawImageFormat::BGRA8;
1769                px.into()
1770            }
1771            RawImageFormat::RG16 => {
1772                let pixels = pixels.get_u16_vec()?;
1773
1774                if pixels.len() != expected_len * TWO_CHANNELS {
1775                    return None;
1776                }
1777
1778                let mut px = vec![0; expected_len * FOUR_BPP];
1779
1780                // TODO: check that this function is SIMD optimized
1781                for (pixel_index, greyalpha) in
1782                    pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate()
1783                {
1784                    let grey_u8 = normalize_u16(greyalpha[0]);
1785                    let alpha_u8 = normalize_u16(greyalpha[1]);
1786
1787                    if alpha_u8 != 255 {
1788                        is_opaque = false;
1789                    }
1790
1791                    px[pixel_index * FOUR_BPP] = grey_u8;
1792                    px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
1793                    px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
1794                    px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
1795                }
1796
1797                data_format = RawImageFormat::BGRA8;
1798                px.into()
1799            }
1800            RawImageFormat::RGB16 => {
1801                let pixels = pixels.get_u16_vec()?;
1802
1803                if pixels.len() != expected_len * THREE_CHANNELS {
1804                    return None;
1805                }
1806
1807                let mut px = vec![0; expected_len * FOUR_BPP];
1808
1809                // TODO: check that this function is SIMD optimized
1810                for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
1811                    let red_u8 = normalize_u16(rgb[0]);
1812                    let green_u8 = normalize_u16(rgb[1]);
1813                    let blue_u8 = normalize_u16(rgb[2]);
1814
1815                    px[pixel_index * FOUR_BPP] = blue_u8;
1816                    px[(pixel_index * FOUR_BPP) + 1] = green_u8;
1817                    px[(pixel_index * FOUR_BPP) + 2] = red_u8;
1818                    px[(pixel_index * FOUR_BPP) + 3] = 0xff;
1819                }
1820
1821                data_format = RawImageFormat::BGRA8;
1822                px.into()
1823            }
1824            RawImageFormat::RGBA16 => {
1825                let pixels = pixels.get_u16_vec()?;
1826
1827                if pixels.len() != expected_len * FOUR_CHANNELS {
1828                    return None;
1829                }
1830
1831                let mut px = vec![0; expected_len * FOUR_BPP];
1832
1833                // TODO: check that this function is SIMD optimized
1834                if premultiplied_alpha {
1835                    for (pixel_index, rgba) in
1836                        pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate()
1837                    {
1838                        let red_u8 = normalize_u16(rgba[0]);
1839                        let green_u8 = normalize_u16(rgba[1]);
1840                        let blue_u8 = normalize_u16(rgba[2]);
1841                        let alpha_u8 = normalize_u16(rgba[3]);
1842
1843                        if alpha_u8 != 255 {
1844                            is_opaque = false;
1845                        }
1846
1847                        px[pixel_index * FOUR_BPP] = blue_u8;
1848                        px[(pixel_index * FOUR_BPP) + 1] = green_u8;
1849                        px[(pixel_index * FOUR_BPP) + 2] = red_u8;
1850                        px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
1851                    }
1852                } else {
1853                    for (pixel_index, rgba) in
1854                        pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate()
1855                    {
1856                        let red_u8 = normalize_u16(rgba[0]);
1857                        let green_u8 = normalize_u16(rgba[1]);
1858                        let blue_u8 = normalize_u16(rgba[2]);
1859                        let alpha_u8 = normalize_u16(rgba[3]);
1860
1861                        if alpha_u8 != 255 {
1862                            is_opaque = false;
1863                        }
1864
1865                        px[pixel_index * FOUR_BPP] = blue_u8;
1866                        px[(pixel_index * FOUR_BPP) + 1] = green_u8;
1867                        px[(pixel_index * FOUR_BPP) + 2] = red_u8;
1868                        px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
1869                        premultiply_alpha(
1870                            &mut px
1871                                [(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
1872                        );
1873                    }
1874                }
1875
1876                data_format = RawImageFormat::BGRA8;
1877                px.into()
1878            }
1879            RawImageFormat::BGR8 => {
1880                let pixels = pixels.get_u8_vec()?;
1881
1882                if pixels.len() != expected_len * THREE_CHANNELS {
1883                    return None;
1884                }
1885
1886                let mut px = vec![0; expected_len * FOUR_BPP];
1887
1888                // TODO: check that this function is SIMD optimized
1889                for (pixel_index, bgr) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
1890                    let blue = bgr[0];
1891                    let green = bgr[1];
1892                    let red = bgr[2];
1893
1894                    px[pixel_index * FOUR_BPP] = blue;
1895                    px[(pixel_index * FOUR_BPP) + 1] = green;
1896                    px[(pixel_index * FOUR_BPP) + 2] = red;
1897                    px[(pixel_index * FOUR_BPP) + 3] = 0xff;
1898                }
1899
1900                data_format = RawImageFormat::BGRA8;
1901                px.into()
1902            }
1903            RawImageFormat::BGRA8 => {
1904                if premultiplied_alpha {
1905                    // DO NOT CLONE THE IMAGE HERE!
1906                    let pixels = pixels.get_u8_vec()?;
1907
1908                    is_opaque = pixels
1909                        .as_ref()
1910                        .chunks_exact(FOUR_CHANNELS)
1911                        .all(|bgra| bgra[3] == 255);
1912
1913                    pixels
1914                } else {
1915                    let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
1916
1917                    if pixels.len() != expected_len * FOUR_BPP {
1918                        return None;
1919                    }
1920
1921                    for bgra in pixels.chunks_exact_mut(FOUR_CHANNELS) {
1922                        if bgra[3] != 255 {
1923                            is_opaque = false;
1924                        }
1925                        premultiply_alpha(bgra);
1926                    }
1927                    data_format = RawImageFormat::BGRA8;
1928                    pixels.into()
1929                }
1930            }
1931            RawImageFormat::RGBF32 => {
1932                let pixels = pixels.get_f32_vec_ref()?;
1933
1934                if pixels.len() != expected_len * THREE_CHANNELS {
1935                    return None;
1936                }
1937
1938                let mut px = vec![0; expected_len * FOUR_BPP];
1939
1940                // TODO: check that this function is SIMD optimized
1941                for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
1942                    let red_u8 = (rgb[0] * 255.0) as u8;
1943                    let green_u8 = (rgb[1] * 255.0) as u8;
1944                    let blue_u8 = (rgb[2] * 255.0) as u8;
1945
1946                    px[pixel_index * FOUR_BPP] = blue_u8;
1947                    px[(pixel_index * FOUR_BPP) + 1] = green_u8;
1948                    px[(pixel_index * FOUR_BPP) + 2] = red_u8;
1949                    px[(pixel_index * FOUR_BPP) + 3] = 0xff;
1950                }
1951
1952                data_format = RawImageFormat::BGRA8;
1953                px.into()
1954            }
1955            RawImageFormat::RGBAF32 => {
1956                let pixels = pixels.get_f32_vec_ref()?;
1957
1958                if pixels.len() != expected_len * FOUR_CHANNELS {
1959                    return None;
1960                }
1961
1962                let mut px = vec![0; expected_len * FOUR_BPP];
1963
1964                // TODO: check that this function is SIMD optimized
1965                if premultiplied_alpha {
1966                    for (pixel_index, rgba) in
1967                        pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate()
1968                    {
1969                        let red_u8 = (rgba[0] * 255.0) as u8;
1970                        let green_u8 = (rgba[1] * 255.0) as u8;
1971                        let blue_u8 = (rgba[2] * 255.0) as u8;
1972                        let alpha_u8 = (rgba[3] * 255.0) as u8;
1973
1974                        if alpha_u8 != 255 {
1975                            is_opaque = false;
1976                        }
1977
1978                        px[pixel_index * FOUR_BPP] = blue_u8;
1979                        px[(pixel_index * FOUR_BPP) + 1] = green_u8;
1980                        px[(pixel_index * FOUR_BPP) + 2] = red_u8;
1981                        px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
1982                    }
1983                } else {
1984                    for (pixel_index, rgba) in
1985                        pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate()
1986                    {
1987                        let red_u8 = (rgba[0] * 255.0) as u8;
1988                        let green_u8 = (rgba[1] * 255.0) as u8;
1989                        let blue_u8 = (rgba[2] * 255.0) as u8;
1990                        let alpha_u8 = (rgba[3] * 255.0) as u8;
1991
1992                        if alpha_u8 != 255 {
1993                            is_opaque = false;
1994                        }
1995
1996                        px[pixel_index * FOUR_BPP] = blue_u8;
1997                        px[(pixel_index * FOUR_BPP) + 1] = green_u8;
1998                        px[(pixel_index * FOUR_BPP) + 2] = red_u8;
1999                        px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2000                        premultiply_alpha(
2001                            &mut px
2002                                [(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2003                        );
2004                    }
2005                }
2006
2007                data_format = RawImageFormat::BGRA8;
2008                px.into()
2009            }
2010        };
2011
2012        let image_data = ImageData::Raw(SharedRawImageData::new(bytes));
2013        let image_descriptor = ImageDescriptor {
2014            format: data_format,
2015            width,
2016            height,
2017            offset: 0,
2018            stride: None.into(),
2019            flags: ImageDescriptorFlags {
2020                is_opaque,
2021                allow_mipmaps: true,
2022            },
2023        };
2024
2025        Some((image_data, image_descriptor))
2026    }
2027}
2028
2029impl_option!(
2030    RawImage,
2031    OptionRawImage,
2032    copy = false,
2033    [Debug, Clone, PartialEq, PartialOrd]
2034);
2035
2036pub fn font_size_to_au(font_size: StyleFontSize) -> Au {
2037    Au::from_px(font_size.inner.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE))
2038}
2039
2040pub type FontInstanceFlags = u32;
2041
2042// Common flags
2043pub const FONT_INSTANCE_FLAG_SYNTHETIC_BOLD: u32 = 1 << 1;
2044pub const FONT_INSTANCE_FLAG_EMBEDDED_BITMAPS: u32 = 1 << 2;
2045pub const FONT_INSTANCE_FLAG_SUBPIXEL_BGR: u32 = 1 << 3;
2046pub const FONT_INSTANCE_FLAG_TRANSPOSE: u32 = 1 << 4;
2047pub const FONT_INSTANCE_FLAG_FLIP_X: u32 = 1 << 5;
2048pub const FONT_INSTANCE_FLAG_FLIP_Y: u32 = 1 << 6;
2049pub const FONT_INSTANCE_FLAG_SUBPIXEL_POSITION: u32 = 1 << 7;
2050
2051// Windows flags
2052pub const FONT_INSTANCE_FLAG_FORCE_GDI: u32 = 1 << 16;
2053
2054// Mac flags
2055pub const FONT_INSTANCE_FLAG_FONT_SMOOTHING: u32 = 1 << 16;
2056
2057// FreeType flags
2058pub const FONT_INSTANCE_FLAG_FORCE_AUTOHINT: u32 = 1 << 16;
2059pub const FONT_INSTANCE_FLAG_NO_AUTOHINT: u32 = 1 << 17;
2060pub const FONT_INSTANCE_FLAG_VERTICAL_LAYOUT: u32 = 1 << 18;
2061pub const FONT_INSTANCE_FLAG_LCD_VERTICAL: u32 = 1 << 19;
2062
2063#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2064pub struct GlyphOptions {
2065    pub render_mode: FontRenderMode,
2066    pub flags: FontInstanceFlags,
2067}
2068
2069#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2070pub enum FontRenderMode {
2071    Mono,
2072    Alpha,
2073    Subpixel,
2074}
2075
2076#[cfg(target_arch = "wasm32")]
2077#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2078pub struct FontInstancePlatformOptions {
2079    // empty for now
2080}
2081
2082#[cfg(target_os = "windows")]
2083#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2084pub struct FontInstancePlatformOptions {
2085    pub gamma: u16,
2086    pub contrast: u8,
2087    pub cleartype_level: u8,
2088}
2089
2090#[cfg(target_os = "macos")]
2091#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2092pub struct FontInstancePlatformOptions {
2093    pub unused: u32,
2094}
2095
2096#[cfg(target_os = "linux")]
2097#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2098pub struct FontInstancePlatformOptions {
2099    pub lcd_filter: FontLCDFilter,
2100    pub hinting: FontHinting,
2101}
2102
2103// Mobile targets — empty platform-options struct keeps the
2104// `FontInstanceOptions { platform_options: Option<...>, .. }` field
2105// well-typed without inheriting Linux's freetype-specific tunables.
2106#[cfg(any(target_os = "android", target_os = "ios"))]
2107#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2108pub struct FontInstancePlatformOptions {
2109    pub unused: u32,
2110}
2111
2112#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2113pub enum FontHinting {
2114    None,
2115    Mono,
2116    Light,
2117    Normal,
2118    LCD,
2119}
2120
2121#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2122pub enum FontLCDFilter {
2123    None,
2124    Default,
2125    Light,
2126    Legacy,
2127}
2128
2129impl Default for FontLCDFilter {
2130    fn default() -> Self {
2131        FontLCDFilter::Default
2132    }
2133}
2134
2135#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2136pub struct FontInstanceOptions {
2137    pub render_mode: FontRenderMode,
2138    pub flags: FontInstanceFlags,
2139    pub bg_color: ColorU,
2140    /// When bg_color.a is != 0 and render_mode is FontRenderMode::Subpixel,
2141    /// the text will be rendered with bg_color.r/g/b as an opaque estimated
2142    /// background color.
2143    pub synthetic_italics: SyntheticItalics,
2144}
2145
2146impl Default for FontInstanceOptions {
2147    fn default() -> FontInstanceOptions {
2148        FontInstanceOptions {
2149            render_mode: FontRenderMode::Subpixel,
2150            flags: 0,
2151            bg_color: ColorU::TRANSPARENT,
2152            synthetic_italics: SyntheticItalics::default(),
2153        }
2154    }
2155}
2156
2157#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2158pub struct SyntheticItalics {
2159    pub angle: i16,
2160}
2161
2162impl Default for SyntheticItalics {
2163    fn default() -> Self {
2164        Self { angle: 0 }
2165    }
2166}
2167
2168/// Reference-counted wrapper around raw image bytes (U8Vec).
2169/// This allows sharing image data between azul-core and webrender without cloning.
2170///
2171/// Similar to ImageRef but specifically for raw byte data, avoiding the overhead
2172/// of the full DecodedImage enum when we just need the bytes.
2173#[derive(Debug)]
2174#[repr(C)]
2175pub struct SharedRawImageData {
2176    /// Shared pointer to the raw image bytes
2177    pub data: *const U8Vec,
2178    /// Reference counter - when it reaches 0, the data is deallocated
2179    pub copies: *const AtomicUsize,
2180    /// Whether to run the destructor (for FFI safety)
2181    pub run_destructor: bool,
2182}
2183
2184impl SharedRawImageData {
2185    /// Create a new SharedRawImageData from a U8Vec
2186    pub fn new(data: U8Vec) -> Self {
2187        Self {
2188            data: Box::into_raw(Box::new(data)),
2189            copies: Box::into_raw(Box::new(AtomicUsize::new(1))),
2190            run_destructor: true,
2191        }
2192    }
2193
2194    /// Get a reference to the underlying bytes
2195    pub fn as_ref(&self) -> &[u8] {
2196        unsafe { (*self.data).as_ref() }
2197    }
2198
2199    /// Alias for as_ref() - get the raw bytes as a slice
2200    pub fn get_bytes(&self) -> &[u8] {
2201        self.as_ref()
2202    }
2203
2204    /// Get a pointer to the raw bytes for hashing/identification
2205    pub fn as_ptr(&self) -> *const u8 {
2206        unsafe { (*self.data).as_ref().as_ptr() }
2207    }
2208
2209    /// Get the length of the data
2210    pub fn len(&self) -> usize {
2211        unsafe { (*self.data).len() }
2212    }
2213
2214    /// Check if the data is empty
2215    pub fn is_empty(&self) -> bool {
2216        self.len() == 0
2217    }
2218
2219    /// Try to extract the U8Vec if this is the only reference
2220    /// Returns None if there are other references
2221    pub fn into_inner(self) -> Option<U8Vec> {
2222        unsafe {
2223            if self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) == Some(1) {
2224                let data = Box::from_raw(self.data as *mut U8Vec);
2225                let _ = Box::from_raw(self.copies as *mut AtomicUsize);
2226                core::mem::forget(self); // don't run the destructor
2227                Some(*data)
2228            } else {
2229                None
2230            }
2231        }
2232    }
2233}
2234
2235unsafe impl Send for SharedRawImageData {}
2236unsafe impl Sync for SharedRawImageData {}
2237
2238impl Clone for SharedRawImageData {
2239    fn clone(&self) -> Self {
2240        unsafe {
2241            self.copies
2242                .as_ref()
2243                .map(|m| m.fetch_add(1, AtomicOrdering::SeqCst));
2244        }
2245        Self {
2246            data: self.data,
2247            copies: self.copies,
2248            run_destructor: true,
2249        }
2250    }
2251}
2252
2253impl Drop for SharedRawImageData {
2254    fn drop(&mut self) {
2255        self.run_destructor = false;
2256        unsafe {
2257            let copies = (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst);
2258            if copies == 1 {
2259                let _ = Box::from_raw(self.data as *mut U8Vec);
2260                let _ = Box::from_raw(self.copies as *mut AtomicUsize);
2261            }
2262        }
2263    }
2264}
2265
2266impl PartialEq for SharedRawImageData {
2267    fn eq(&self, rhs: &Self) -> bool {
2268        self.data as usize == rhs.data as usize
2269    }
2270}
2271
2272impl Eq for SharedRawImageData {}
2273
2274impl PartialOrd for SharedRawImageData {
2275    fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
2276        Some(self.cmp(other))
2277    }
2278}
2279
2280impl Ord for SharedRawImageData {
2281    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2282        (self.data as usize).cmp(&(other.data as usize))
2283    }
2284}
2285
2286impl Hash for SharedRawImageData {
2287    fn hash<H>(&self, state: &mut H)
2288    where
2289        H: Hasher,
2290    {
2291        (self.data as usize).hash(state)
2292    }
2293}
2294
2295/// Represents the backing store of an arbitrary series of pixels for display by
2296/// WebRender. This storage can take several forms.
2297#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2298#[repr(C, u8)]
2299pub enum ImageData {
2300    /// A simple series of bytes, provided by the embedding and owned by WebRender.
2301    /// The format is stored out-of-band, currently in ImageDescriptor.
2302    Raw(SharedRawImageData),
2303    /// An image owned by the embedding, and referenced by WebRender. This may
2304    /// take the form of a texture or a heap-allocated buffer.
2305    External(ExternalImageData),
2306}
2307
2308/// Storage format identifier for externally-managed images.
2309#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
2310#[repr(C, u8)]
2311pub enum ExternalImageType {
2312    /// The image is texture-backed.
2313    TextureHandle(ImageBufferKind),
2314    /// The image is heap-allocated by the embedding.
2315    Buffer,
2316}
2317
2318/// An arbitrary identifier for an external image provided by the
2319/// application. It must be a unique identifier for each external
2320/// image.
2321#[repr(C)]
2322#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
2323pub struct ExternalImageId {
2324    pub inner: u64,
2325}
2326
2327static LAST_EXTERNAL_IMAGE_ID: AtomicUsize = AtomicUsize::new(0);
2328
2329impl ExternalImageId {
2330    /// Creates a new, unique ExternalImageId
2331    pub fn new() -> Self {
2332        Self {
2333            inner: LAST_EXTERNAL_IMAGE_ID.fetch_add(1, AtomicOrdering::SeqCst) as u64,
2334        }
2335    }
2336}
2337
2338#[derive(Debug, Clone, PartialEq, PartialOrd)]
2339#[repr(C, u8)]
2340pub enum GlyphOutlineOperation {
2341    MoveTo(OutlineMoveTo),
2342    LineTo(OutlineLineTo),
2343    QuadraticCurveTo(OutlineQuadTo),
2344    CubicCurveTo(OutlineCubicTo),
2345    ClosePath,
2346}
2347
2348impl_option!(
2349    GlyphOutlineOperation,
2350    OptionGlyphOutlineOperation,
2351    copy = false,
2352    [Debug, Clone, PartialEq, PartialOrd]
2353);
2354
2355// MoveTo in em units
2356#[derive(Debug, Clone, PartialEq, PartialOrd)]
2357#[repr(C)]
2358pub struct OutlineMoveTo {
2359    pub x: i16,
2360    pub y: i16,
2361}
2362
2363// LineTo in em units
2364#[derive(Debug, Clone, PartialEq, PartialOrd)]
2365#[repr(C)]
2366pub struct OutlineLineTo {
2367    pub x: i16,
2368    pub y: i16,
2369}
2370
2371// QuadTo in em units
2372#[derive(Debug, Clone, PartialEq, PartialOrd)]
2373#[repr(C)]
2374pub struct OutlineQuadTo {
2375    pub ctrl_1_x: i16,
2376    pub ctrl_1_y: i16,
2377    pub end_x: i16,
2378    pub end_y: i16,
2379}
2380
2381// CubicTo in em units
2382#[derive(Debug, Clone, PartialEq, PartialOrd)]
2383#[repr(C)]
2384pub struct OutlineCubicTo {
2385    pub ctrl_1_x: i16,
2386    pub ctrl_1_y: i16,
2387    pub ctrl_2_x: i16,
2388    pub ctrl_2_y: i16,
2389    pub end_x: i16,
2390    pub end_y: i16,
2391}
2392
2393#[derive(Debug, Clone, PartialEq, PartialOrd)]
2394#[repr(C)]
2395pub struct GlyphOutline {
2396    pub operations: GlyphOutlineOperationVec,
2397}
2398
2399azul_css::impl_vec!(GlyphOutlineOperation, GlyphOutlineOperationVec, GlyphOutlineOperationVecDestructor, GlyphOutlineOperationVecDestructorType, GlyphOutlineOperationVecSlice, OptionGlyphOutlineOperation);
2400azul_css::impl_vec_clone!(
2401    GlyphOutlineOperation,
2402    GlyphOutlineOperationVec,
2403    GlyphOutlineOperationVecDestructor
2404);
2405azul_css::impl_vec_debug!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2406azul_css::impl_vec_partialord!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2407azul_css::impl_vec_partialeq!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2408
2409#[derive(Debug, Clone)]
2410#[repr(C)]
2411pub struct OwnedGlyphBoundingBox {
2412    pub max_x: i16,
2413    pub max_y: i16,
2414    pub min_x: i16,
2415    pub min_y: i16,
2416}
2417
2418/// Specifies the type of texture target in driver terms.
2419#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
2420#[repr(C)]
2421pub enum ImageBufferKind {
2422    /// Standard texture. This maps to GL_TEXTURE_2D in OpenGL.
2423    Texture2D = 0,
2424    /// Rectangle texture. This maps to GL_TEXTURE_RECTANGLE in OpenGL. This
2425    /// is similar to a standard texture, with a few subtle differences
2426    /// (no mipmaps, non-power-of-two dimensions, different coordinate space)
2427    /// that make it useful for representing the kinds of textures we use
2428    /// in WebRender. See https://www.khronos.org/opengl/wiki/Rectangle_Texture
2429    /// for background on Rectangle textures.
2430    TextureRect = 1,
2431    /// External texture. This maps to GL_TEXTURE_EXTERNAL_OES in OpenGL, which
2432    /// is an extension. This is used for image formats that OpenGL doesn't
2433    /// understand, particularly YUV. See
2434    /// https://www.khronos.org/registry/OpenGL/extensions/OES/OES_EGL_image_external.txt
2435    TextureExternal = 2,
2436}
2437
2438/// Descriptor for external image resources. See `ImageData`.
2439#[repr(C)]
2440#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
2441pub struct ExternalImageData {
2442    /// The identifier of this external image, provided by the embedding.
2443    pub id: ExternalImageId,
2444    /// For multi-plane images (i.e. YUV), indicates the plane of the
2445    /// original image that this struct represents. 0 for single-plane images.
2446    pub channel_index: u8,
2447    /// Storage format identifier.
2448    pub image_type: ExternalImageType,
2449}
2450
2451pub type TileSize = u16;
2452
2453#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
2454pub enum ImageDirtyRect {
2455    All,
2456    Partial(LayoutRect),
2457}
2458
2459#[derive(Debug, Clone, PartialEq, PartialOrd)]
2460pub enum ResourceUpdate {
2461    AddFont(AddFont),
2462    DeleteFont(FontKey),
2463    AddFontInstance(AddFontInstance),
2464    DeleteFontInstance(FontInstanceKey),
2465    AddImage(AddImage),
2466    UpdateImage(UpdateImage),
2467    DeleteImage(ImageKey),
2468}
2469
2470#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2471pub struct AddImage {
2472    pub key: ImageKey,
2473    pub descriptor: ImageDescriptor,
2474    pub data: ImageData,
2475    pub tiling: Option<TileSize>,
2476}
2477
2478#[derive(Debug, Clone, PartialEq, PartialOrd)]
2479pub struct UpdateImage {
2480    pub key: ImageKey,
2481    pub descriptor: ImageDescriptor,
2482    pub data: ImageData,
2483    pub dirty_rect: ImageDirtyRect,
2484}
2485
2486/// Message to add a font to WebRender.
2487/// Contains a reference to the parsed font data.
2488#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2489pub struct AddFont {
2490    pub key: FontKey,
2491    pub font: azul_css::props::basic::FontRef,
2492}
2493
2494impl fmt::Debug for AddFont {
2495    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2496        write!(
2497            f,
2498            "AddFont {{ key: {:?}, font: {:?} }}",
2499            self.key, self.font
2500        )
2501    }
2502}
2503
2504#[derive(Debug, Clone, PartialEq, PartialOrd)]
2505pub struct AddFontInstance {
2506    pub key: FontInstanceKey,
2507    pub font_key: FontKey,
2508    pub glyph_size: (Au, DpiScaleFactor),
2509    pub options: Option<FontInstanceOptions>,
2510    pub platform_options: Option<FontInstancePlatformOptions>,
2511    pub variations: Vec<FontVariation>,
2512}
2513
2514#[repr(C)]
2515#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
2516pub struct FontVariation {
2517    pub tag: u32,
2518    pub value: f32,
2519}
2520
2521#[repr(C)]
2522#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
2523pub struct Epoch {
2524    inner: u32,
2525}
2526
2527impl fmt::Display for Epoch {
2528    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2529        write!(f, "{}", self.inner)
2530    }
2531}
2532
2533impl Epoch {
2534    // prevent raw access to the .inner field so that
2535    // you can grep the codebase for .increment() to see
2536    // exactly where the epoch is being incremented
2537    pub const fn new() -> Self {
2538        Self { inner: 0 }
2539    }
2540    pub const fn from(i: u32) -> Self {
2541        Self { inner: i }
2542    }
2543    pub const fn into_u32(&self) -> u32 {
2544        self.inner
2545    }
2546
2547    // We don't want the epoch to increase to u32::MAX, since
2548    // u32::MAX represents an invalid epoch, which could confuse webrender
2549    pub fn increment(&mut self) {
2550        use core::u32;
2551        const MAX_ID: u32 = u32::MAX - 1;
2552        *self = match self.inner {
2553            MAX_ID => Epoch { inner: 0 },
2554            other => Epoch {
2555                inner: other.saturating_add(1),
2556            },
2557        };
2558    }
2559}
2560
2561// App units that this font instance was registered for
2562#[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Eq, Ord)]
2563pub struct Au(pub i32);
2564
2565pub const AU_PER_PX: i32 = 60;
2566pub const MAX_AU: i32 = (1 << 30) - 1;
2567pub const MIN_AU: i32 = -(1 << 30) - 1;
2568
2569impl Au {
2570    pub fn from_px(px: f32) -> Self {
2571        let target_app_units = (px * AU_PER_PX as f32) as i32;
2572        Au(target_app_units.min(MAX_AU).max(MIN_AU))
2573    }
2574    pub fn into_px(&self) -> f32 {
2575        self.0 as f32 / AU_PER_PX as f32
2576    }
2577}
2578
2579// Debug, PartialEq, Eq, PartialOrd, Ord
2580#[derive(Debug)]
2581pub enum AddFontMsg {
2582    // add font: font key, font bytes + font index
2583    Font(FontKey, StyleFontFamilyHash, FontRef),
2584    Instance(AddFontInstance, (Au, DpiScaleFactor)),
2585}
2586
2587impl AddFontMsg {
2588    pub fn into_resource_update(&self) -> ResourceUpdate {
2589        use self::AddFontMsg::*;
2590        match self {
2591            Font(font_key, _, font_ref) => ResourceUpdate::AddFont(AddFont {
2592                key: *font_key,
2593                font: font_ref.clone(),
2594            }),
2595            Instance(fi, _) => ResourceUpdate::AddFontInstance(fi.clone()),
2596        }
2597    }
2598}
2599
2600#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
2601pub enum DeleteFontMsg {
2602    Font(FontKey),
2603    Instance(FontInstanceKey, (Au, DpiScaleFactor)),
2604}
2605
2606impl DeleteFontMsg {
2607    pub fn into_resource_update(&self) -> ResourceUpdate {
2608        use self::DeleteFontMsg::*;
2609        match self {
2610            Font(f) => ResourceUpdate::DeleteFont(*f),
2611            Instance(fi, _) => ResourceUpdate::DeleteFontInstance(*fi),
2612        }
2613    }
2614}
2615
2616#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
2617pub struct AddImageMsg(pub AddImage);
2618
2619impl AddImageMsg {
2620    pub fn into_resource_update(&self) -> ResourceUpdate {
2621        ResourceUpdate::AddImage(self.0.clone())
2622    }
2623}
2624
2625#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2626#[repr(C)]
2627pub struct LoadedFontSource {
2628    pub data: U8Vec,
2629    pub index: u32,
2630    pub load_outlines: bool,
2631}
2632
2633// function to load the font source from a file
2634pub type LoadFontFn = fn(&StyleFontFamily, &FcFontCache) -> Option<LoadedFontSource>;
2635
2636// function to parse the font given the loaded font source
2637pub type ParseFontFn = fn(LoadedFontSource) -> Option<FontRef>; // = Option<Box<azul_text_layout::Font>>
2638
2639pub type GlStoreImageFn = fn(DocumentId, Epoch, Texture, ExternalImageId);
2640
2641/// Compute the deterministic `ExternalImageId` that the OpenGL texture cache uses
2642/// for a texture bound to a specific DOM node. The same `(DomId, NodeId)` always
2643/// maps to the same `ExternalImageId`, so cached display lists keep working across
2644/// frames.
2645pub fn texture_external_image_id(dom_id: DomId, node_id: NodeId) -> ExternalImageId {
2646    let dom = dom_id.inner as u64;
2647    let node = node_id.index() as u64;
2648    debug_assert!(dom <= u32::MAX as u64, "DomId exceeds 32-bit range");
2649    debug_assert!(node <= u32::MAX as u64, "NodeId exceeds 32-bit range");
2650    ExternalImageId {
2651        inner: (dom << 32) | (node & 0xFFFFFFFF),
2652    }
2653}
2654
2655/// Compute the `ExternalImageId` for a static GL texture identified by its
2656/// `ImageRefHash`. Mirrors `image_ref_hash_to_image_key` so a given image hash
2657/// produces the same identifiers everywhere.
2658pub fn image_ref_hash_to_external_image_id(hash: ImageRefHash) -> ExternalImageId {
2659    ExternalImageId {
2660        inner: hash.inner as u64,
2661    }
2662}
2663
2664/// Given the fonts of the current frame, returns `AddFont` and `AddFontInstance`s of
2665/// which fonts / instances are currently not in the `current_registered_fonts` and
2666/// need to be added.
2667///
2668/// Deleting fonts can only be done after the entire frame has finished drawing,
2669/// otherwise (if removing fonts would happen after every DOM) we'd constantly
2670/// add-and-remove fonts after every VirtualViewCallback, which would cause a lot of
2671/// I/O waiting.
2672pub fn build_add_font_resource_updates(
2673    renderer_resources: &mut RendererResources,
2674    dpi: DpiScaleFactor,
2675    fc_cache: &FcFontCache,
2676    id_namespace: IdNamespace,
2677    fonts_in_dom: &OrderedMap<ImmediateFontId, FastBTreeSet<Au>>,
2678    font_source_load_fn: LoadFontFn,
2679    parse_font_fn: ParseFontFn,
2680) -> Vec<(StyleFontFamilyHash, AddFontMsg)> {
2681    let mut resource_updates = alloc::vec::Vec::new();
2682    let mut font_instances_added_this_frame = FastBTreeSet::new();
2683
2684    'outer: for (im_font_id, font_sizes) in fonts_in_dom {
2685        macro_rules! insert_font_instances {
2686            ($font_family_hash:expr, $font_key:expr, $font_size:expr) => {{
2687                let font_instance_key_exists = renderer_resources
2688                    .currently_registered_fonts
2689                    .get(&$font_key)
2690                    .and_then(|(_, font_instances)| font_instances.get(&($font_size, dpi)))
2691                    .is_some()
2692                    || font_instances_added_this_frame.contains(&($font_key, ($font_size, dpi)));
2693
2694                if !font_instance_key_exists {
2695                    let font_instance_key = FontInstanceKey::unique(id_namespace);
2696
2697                    // For some reason the gamma is way to low on Windows
2698                    #[cfg(target_os = "windows")]
2699                    let platform_options = FontInstancePlatformOptions {
2700                        gamma: 300,
2701                        contrast: 100,
2702                        cleartype_level: 100,
2703                    };
2704
2705                    #[cfg(target_os = "linux")]
2706                    let platform_options = FontInstancePlatformOptions {
2707                        lcd_filter: FontLCDFilter::Default,
2708                        hinting: FontHinting::Normal,
2709                    };
2710
2711                    #[cfg(target_os = "macos")]
2712                    let platform_options = FontInstancePlatformOptions::default();
2713
2714                    #[cfg(target_arch = "wasm32")]
2715                    let platform_options = FontInstancePlatformOptions::default();
2716
2717                    #[cfg(any(target_os = "android", target_os = "ios"))]
2718                    let platform_options = FontInstancePlatformOptions::default();
2719
2720                    let options = FontInstanceOptions {
2721                        render_mode: FontRenderMode::Subpixel,
2722                        flags: FONT_INSTANCE_FLAG_NO_AUTOHINT,
2723                        ..Default::default()
2724                    };
2725
2726                    font_instances_added_this_frame.insert(($font_key, ($font_size, dpi)));
2727                    resource_updates.push((
2728                        $font_family_hash,
2729                        AddFontMsg::Instance(
2730                            AddFontInstance {
2731                                key: font_instance_key,
2732                                font_key: $font_key,
2733                                glyph_size: ($font_size, dpi),
2734                                options: Some(options),
2735                                platform_options: Some(platform_options),
2736                                variations: alloc::vec::Vec::new(),
2737                            },
2738                            ($font_size, dpi),
2739                        ),
2740                    ));
2741                }
2742            }};
2743        }
2744
2745        match im_font_id {
2746            ImmediateFontId::Resolved((font_family_hash, font_id)) => {
2747                // nothing to do, font is already added,
2748                // just insert the missing font instances
2749                for font_size in font_sizes.iter() {
2750                    insert_font_instances!(*font_family_hash, *font_id, *font_size);
2751                }
2752            }
2753            ImmediateFontId::Unresolved(style_font_families) => {
2754                // If the font is already loaded during the current frame,
2755                // do not attempt to load it again
2756                //
2757                // This prevents duplicated loading for fonts in different orders, i.e.
2758                // - vec!["Times New Roman", "serif"] and
2759                // - vec!["sans", "Times New Roman"]
2760                // ... will resolve to the same font instead of creating two fonts
2761
2762                // If there is no font key, that means there's also no font instances
2763                let mut font_family_hash = None;
2764                let font_families_hash = StyleFontFamiliesHash::new(style_font_families.as_ref());
2765
2766                // Find the first font that can be loaded and parsed
2767                'inner: for family in style_font_families.as_ref().iter() {
2768                    let current_family_hash = StyleFontFamilyHash::new(&family);
2769
2770                    if let Some(font_id) = renderer_resources.font_id_map.get(&current_family_hash)
2771                    {
2772                        // font key already exists
2773                        for font_size in font_sizes {
2774                            insert_font_instances!(current_family_hash, *font_id, *font_size);
2775                        }
2776                        continue 'outer;
2777                    }
2778
2779                    let font_ref = match family {
2780                        StyleFontFamily::Ref(r) => r.clone(), // Clone the FontRef
2781                        other => {
2782                            // Load and parse the font
2783                            let font_data = match (font_source_load_fn)(&other, fc_cache) {
2784                                Some(s) => s,
2785                                None => continue 'inner,
2786                            };
2787
2788                            let font_ref = match (parse_font_fn)(font_data) {
2789                                Some(s) => s,
2790                                None => continue 'inner,
2791                            };
2792
2793                            font_ref
2794                        }
2795                    };
2796
2797                    // font loaded properly
2798                    font_family_hash = Some((current_family_hash, font_ref));
2799                    break 'inner;
2800                }
2801
2802                let (font_family_hash, font_ref) = match font_family_hash {
2803                    None => continue 'outer, // No font could be loaded, try again next frame
2804                    Some(s) => s,
2805                };
2806
2807                // Generate a new font key, store the mapping between hash and font key
2808                let font_key = FontKey::unique(id_namespace);
2809                let add_font_msg = AddFontMsg::Font(font_key, font_family_hash, font_ref);
2810
2811                renderer_resources
2812                    .font_id_map
2813                    .insert(font_family_hash, font_key);
2814                renderer_resources
2815                    .font_families_map
2816                    .insert(font_families_hash, font_family_hash);
2817                resource_updates.push((font_family_hash, add_font_msg));
2818
2819                // Insert font sizes for the newly generated font key
2820                for font_size in font_sizes {
2821                    insert_font_instances!(font_family_hash, font_key, *font_size);
2822                }
2823            }
2824        }
2825    }
2826
2827    resource_updates
2828}
2829
2830/// Given the images of the current frame, returns `AddImage`s of
2831/// which image keys are currently not in the `current_registered_images` and
2832/// need to be added.
2833///
2834/// Returns Vec<(ImageRefHash, AddImageMsg)> where:
2835/// - ImageRefHash: Stable hash of the ImageRef pointer
2836/// - AddImageMsg: Message to add the image to WebRender
2837///
2838/// The ImageKey in AddImageMsg is generated directly from the ImageRefHash using
2839/// image_ref_hash_to_image_key(), so no separate mapping table is needed.
2840///
2841/// Deleting images can only be done after the entire frame has finished drawing,
2842/// otherwise (if removing images would happen after every DOM) we'd constantly
2843/// add-and-remove images after every VirtualViewCallback, which would cause a lot of
2844/// I/O waiting.
2845#[allow(unused_variables)]
2846pub fn build_add_image_resource_updates(
2847    renderer_resources: &RendererResources,
2848    id_namespace: IdNamespace,
2849    epoch: Epoch,
2850    document_id: &DocumentId,
2851    images_in_dom: &FastBTreeSet<ImageRef>,
2852    insert_into_active_gl_textures: GlStoreImageFn,
2853) -> Vec<(ImageRefHash, AddImageMsg)> {
2854    images_in_dom
2855        .iter()
2856        .filter_map(|image_ref| {
2857            let image_ref_hash = image_ref_get_hash(&image_ref);
2858
2859            if renderer_resources
2860                .currently_registered_images
2861                .contains_key(&image_ref_hash)
2862            {
2863                return None;
2864            }
2865
2866            // NOTE: The image_ref.clone() is a shallow clone,
2867            // does not actually clone the data
2868            match image_ref.get_data() {
2869                DecodedImage::Gl(texture) => {
2870                    let descriptor = texture.get_descriptor();
2871                    let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
2872                    // The ExternalImageId is derived from the same stable hash that
2873                    // produces the ImageKey, so the GL texture cache and WebRender
2874                    // agree on a single identifier for this texture.
2875                    let external_image_id = image_ref_hash_to_external_image_id(image_ref_hash);
2876                    // NOTE: The texture is not really cloned here,
2877                    (insert_into_active_gl_textures)(
2878                        *document_id,
2879                        epoch,
2880                        texture.clone(),
2881                        external_image_id,
2882                    );
2883                    Some((
2884                        image_ref_hash,
2885                        AddImageMsg(AddImage {
2886                            key,
2887                            data: ImageData::External(ExternalImageData {
2888                                id: external_image_id,
2889                                channel_index: 0,
2890                                image_type: ExternalImageType::TextureHandle(
2891                                    ImageBufferKind::Texture2D,
2892                                ),
2893                            }),
2894                            descriptor,
2895                            tiling: None,
2896                        }),
2897                    ))
2898                }
2899                DecodedImage::Raw((descriptor, data)) => {
2900                    let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
2901                    Some((
2902                        image_ref_hash,
2903                        AddImageMsg(AddImage {
2904                            key,
2905                            data: data.clone(), // deep-copy except in the &'static case
2906                            descriptor: descriptor.clone(), /* deep-copy, but struct is not very
2907                                                 * large */
2908                            tiling: None,
2909                        }),
2910                    ))
2911                }
2912                DecodedImage::NullImage {
2913                    width: _,
2914                    height: _,
2915                    format: _,
2916                    tag: _,
2917                } => None,
2918                DecodedImage::Callback(_) => None, /* Texture callbacks are handled after layout
2919                                                    * is done */
2920            }
2921        })
2922        .collect()
2923}
2924
2925/// Submits the `AddFont`, `AddFontInstance` and `AddImage` resources to the RenderApi.
2926/// Extends `currently_registered_images` and `currently_registered_fonts` by the
2927/// `last_frame_image_keys` and `last_frame_font_keys`, so that we don't lose track of
2928/// what font and image keys are currently in the API.
2929pub fn add_resources(
2930    renderer_resources: &mut RendererResources,
2931    all_resource_updates: &mut Vec<ResourceUpdate>,
2932    add_font_resources: Vec<(StyleFontFamilyHash, AddFontMsg)>,
2933    add_image_resources: Vec<(ImageRefHash, AddImageMsg)>,
2934) {
2935    all_resource_updates.extend(
2936        add_font_resources
2937            .iter()
2938            .map(|(_, f)| f.into_resource_update()),
2939    );
2940    all_resource_updates.extend(
2941        add_image_resources
2942            .iter()
2943            .map(|(_, i)| i.into_resource_update()),
2944    );
2945
2946    for (image_ref_hash, add_image_msg) in add_image_resources.iter() {
2947        renderer_resources.currently_registered_images.insert(
2948            *image_ref_hash,
2949            ResolvedImage {
2950                key: add_image_msg.0.key,
2951                descriptor: add_image_msg.0.descriptor,
2952            },
2953        );
2954    }
2955
2956    for (_, add_font_msg) in add_font_resources {
2957        use self::AddFontMsg::*;
2958        match add_font_msg {
2959            Font(fk, font_family_hash, font_ref) => {
2960                renderer_resources
2961                    .currently_registered_fonts
2962                    .entry(fk)
2963                    .or_insert_with(|| (font_ref.clone(), OrderedMap::default()));
2964
2965                // CRITICAL: Map font_hash to FontKey so we can look it up during rendering
2966                renderer_resources
2967                    .font_hash_map
2968                    .insert(font_ref.get_hash(), fk);
2969            }
2970            Instance(fi, size) => {
2971                if let Some((_, instances)) = renderer_resources
2972                    .currently_registered_fonts
2973                    .get_mut(&fi.font_key)
2974                {
2975                    instances.insert(size, fi.key);
2976                }
2977            }
2978        }
2979    }
2980}