1#[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 codegen::format::GetHash,
23 props::basic::{
24 pixel::DEFAULT_FONT_SIZE, ColorU, FloatValue, FontRef, LayoutRect, LayoutSize,
25 StyleFontFamily, StyleFontFamilyVec, StyleFontSize,
26 },
27 props::style::scrollbar::OptionScrollPhysics,
28 system::SystemStyle,
29 AzString, F32Vec, LayoutDebugMessage, OptionI32, StringVec, U16Vec, U32Vec, U8Vec,
30};
31use rust_fontconfig::FcFontCache;
32
33pub use crate::callbacks::{
35 CoreImageCallback, CoreRenderImageCallback, CoreRenderImageCallbackType,
36};
37use crate::{
38 callbacks::{LayoutCallback, VirtualViewCallback},
39 dom::{DomId, NodeData, NodeType},
40 geom::{LogicalPosition, LogicalRect, LogicalSize},
41 gl::{OptionGlContextPtr, Texture},
42 hit_test::DocumentId,
43 id::NodeId,
44 prop_cache::CssPropertyCache,
45 refany::RefAny,
46 styled_dom::{
47 NodeHierarchyItemId, StyleFontFamiliesHash, StyleFontFamilyHash, StyledDom, StyledNodeState,
48 },
49 ui_solver::GlyphInstance,
50 window::{AzStringPair, OptionChar, StringPairVec},
51 xml::{
52 ComponentDef, ComponentDefVec, ComponentId, ComponentLibrary, ComponentLibraryVec,
53 ComponentSource, RegisterComponentFn, RegisterComponentLibraryFn,
54 },
55 FastBTreeSet, OrderedMap,
56};
57
58#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
64#[repr(C)]
65pub enum UpdateImageType {
66 Background,
68 Content,
70}
71
72#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
73#[repr(C)]
74pub struct DpiScaleFactor {
75 pub inner: FloatValue,
76}
77
78impl DpiScaleFactor {
79 #[must_use] pub fn new(f: f32) -> Self {
80 Self {
81 inner: FloatValue::new(f),
82 }
83 }
84}
85
86#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
88#[repr(C)]
89#[derive(Default)]
90pub enum AppTerminationBehavior {
91 ReturnToMain,
95 RunForever,
98 #[default]
101 EndProcess,
102}
103
104
105#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
108#[repr(C)]
109pub struct NamedFont {
110 pub name: AzString,
112 pub bytes: U8Vec,
114}
115
116impl_option!(
117 NamedFont,
118 OptionNamedFont,
119 copy = false,
120 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
121);
122
123impl NamedFont {
124 #[must_use] pub const fn new(name: AzString, bytes: U8Vec) -> Self {
125 Self { name, bytes }
126 }
127}
128
129impl_vec!(NamedFont, NamedFontVec, NamedFontVecDestructor, NamedFontVecDestructorType, NamedFontVecSlice, OptionNamedFont);
130impl_vec_mut!(NamedFont, NamedFontVec);
131impl_vec_debug!(NamedFont, NamedFontVec);
132impl_vec_partialeq!(NamedFont, NamedFontVec);
133impl_vec_eq!(NamedFont, NamedFontVec);
134impl_vec_partialord!(NamedFont, NamedFontVec);
135impl_vec_ord!(NamedFont, NamedFontVec);
136impl_vec_hash!(NamedFont, NamedFontVec);
137impl_vec_clone!(NamedFont, NamedFontVec, NamedFontVecDestructor);
138
139#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
148#[repr(C)]
149pub struct LoadedFont {
150 pub font_hash: u64,
154 pub family_name: AzString,
157 pub num_glyphs: u32,
159 pub has_bytes: bool,
164}
165
166impl_option!(
167 LoadedFont,
168 OptionLoadedFont,
169 copy = false,
170 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
171);
172
173impl LoadedFont {
174 #[must_use] pub const fn new(font_hash: u64, family_name: AzString, num_glyphs: u32, has_bytes: bool) -> Self {
175 Self {
176 font_hash,
177 family_name,
178 num_glyphs,
179 has_bytes,
180 }
181 }
182}
183
184impl_vec!(LoadedFont, LoadedFontVec, LoadedFontVecDestructor, LoadedFontVecDestructorType, LoadedFontVecSlice, OptionLoadedFont);
185impl_vec_mut!(LoadedFont, LoadedFontVec);
186impl_vec_debug!(LoadedFont, LoadedFontVec);
187impl_vec_partialeq!(LoadedFont, LoadedFontVec);
188impl_vec_eq!(LoadedFont, LoadedFontVec);
189impl_vec_partialord!(LoadedFont, LoadedFontVec);
190impl_vec_ord!(LoadedFont, LoadedFontVec);
191impl_vec_hash!(LoadedFont, LoadedFontVec);
192impl_vec_clone!(LoadedFont, LoadedFontVec, LoadedFontVecDestructor);
193#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
196#[repr(C, u8)]
197#[derive(Default)]
198pub enum FontLoadingConfig {
199 #[default]
201 LoadAllSystemFonts,
202 LoadOnlyFamilies(StringVec),
205 BundledFontsOnly,
207}
208
209
210#[derive(Debug, Clone, Default)]
239#[repr(C)]
240pub struct CssMockEnvironment {
241 pub theme: azul_css::dynamic_selector::OptionThemeCondition,
243 pub language: azul_css::OptionString,
245 pub os_version: azul_css::dynamic_selector::OptionOsVersion,
247 pub os: azul_css::dynamic_selector::OptionOsCondition,
249 pub desktop_env: azul_css::dynamic_selector::OptionLinuxDesktopEnv,
251 pub viewport_width: azul_css::OptionF32,
254 pub viewport_height: azul_css::OptionF32,
255 pub prefers_reduced_motion: azul_css::OptionBool,
257 pub prefers_high_contrast: azul_css::OptionBool,
259}
260
261impl CssMockEnvironment {
262 #[must_use] pub fn linux() -> Self {
264 Self {
265 os: azul_css::dynamic_selector::OptionOsCondition::Some(azul_css::dynamic_selector::OsCondition::Linux),
266 ..Default::default()
267 }
268 }
269
270 #[must_use] pub fn windows() -> Self {
272 Self {
273 os: azul_css::dynamic_selector::OptionOsCondition::Some(azul_css::dynamic_selector::OsCondition::Windows),
274 ..Default::default()
275 }
276 }
277
278 #[must_use] pub fn macos() -> Self {
280 Self {
281 os: azul_css::dynamic_selector::OptionOsCondition::Some(azul_css::dynamic_selector::OsCondition::MacOS),
282 ..Default::default()
283 }
284 }
285
286 #[must_use] pub fn dark_theme() -> Self {
288 Self {
289 theme: azul_css::dynamic_selector::OptionThemeCondition::Some(azul_css::dynamic_selector::ThemeCondition::Dark),
290 ..Default::default()
291 }
292 }
293
294 #[must_use] pub fn light_theme() -> Self {
296 Self {
297 theme: azul_css::dynamic_selector::OptionThemeCondition::Some(azul_css::dynamic_selector::ThemeCondition::Light),
298 ..Default::default()
299 }
300 }
301
302 pub fn apply_to(&self, ctx: &mut azul_css::dynamic_selector::DynamicSelectorContext) {
304 if let azul_css::dynamic_selector::OptionOsCondition::Some(os) = self.os {
305 ctx.os = os;
306 }
307 if let azul_css::dynamic_selector::OptionOsVersion::Some(os_version) = self.os_version {
308 ctx.os_version = os_version;
309 }
310 if let azul_css::dynamic_selector::OptionLinuxDesktopEnv::Some(de) = self.desktop_env {
311 ctx.desktop_env = azul_css::dynamic_selector::OptionLinuxDesktopEnv::Some(de);
312 }
313 if let azul_css::dynamic_selector::OptionThemeCondition::Some(ref theme) = self.theme {
314 ctx.theme = theme.clone();
315 }
316 if let azul_css::OptionString::Some(ref lang) = self.language {
317 ctx.language = lang.clone();
318 }
319 if let azul_css::OptionBool::Some(reduced) = self.prefers_reduced_motion {
320 ctx.prefers_reduced_motion = if reduced {
321 azul_css::dynamic_selector::BoolCondition::True
322 } else {
323 azul_css::dynamic_selector::BoolCondition::False
324 };
325 }
326 if let azul_css::OptionBool::Some(high_contrast) = self.prefers_high_contrast {
327 ctx.prefers_high_contrast = if high_contrast {
328 azul_css::dynamic_selector::BoolCondition::True
329 } else {
330 azul_css::dynamic_selector::BoolCondition::False
331 };
332 }
333 if let azul_css::OptionF32::Some(w) = self.viewport_width {
334 ctx.viewport_width = w;
335 }
336 if let azul_css::OptionF32::Some(h) = self.viewport_height {
337 ctx.viewport_height = h;
338 }
339 }
340}
341
342impl_option!(
343 CssMockEnvironment,
344 OptionCssMockEnvironment,
345 copy = false,
346 [Debug, Clone]
347);
348
349#[repr(C)]
366pub struct Route {
367 pub pattern: AzString,
369 pub layout_callback: LayoutCallback,
371}
372
373impl Clone for Route {
374 fn clone(&self) -> Self {
375 Self { pattern: self.pattern.clone(), layout_callback: self.layout_callback.clone() }
376 }
377}
378impl fmt::Debug for Route {
379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380 f.debug_struct("Route")
381 .field("pattern", &self.pattern)
382 .field("layout_callback", &self.layout_callback)
383 .finish()
384 }
385}
386impl PartialEq for Route { fn eq(&self, o: &Self) -> bool { self.pattern == o.pattern && self.layout_callback == o.layout_callback } }
387impl Eq for Route {}
388impl PartialOrd for Route { fn partial_cmp(&self, o: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(o)) } }
389impl 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)) } }
390impl Hash for Route { fn hash<H: Hasher>(&self, state: &mut H) { self.pattern.hash(state); self.layout_callback.hash(state); } }
391
392impl_option!(Route, OptionRoute, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
393impl_vec!(Route, RouteVec, RouteVecDestructor, RouteVecDestructorType, RouteVecSlice, OptionRoute);
394impl_vec_mut!(Route, RouteVec);
395impl_vec_debug!(Route, RouteVec);
396impl_vec_clone!(Route, RouteVec, RouteVecDestructor);
397impl_vec_partialeq!(Route, RouteVec);
398impl_vec_eq!(Route, RouteVec);
399impl_vec_partialord!(Route, RouteVec);
400impl_vec_ord!(Route, RouteVec);
401impl_vec_hash!(Route, RouteVec);
402
403#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
408#[repr(C)]
409pub struct RouteMatch {
410 pub pattern: AzString,
412 pub params: StringPairVec,
414}
415
416impl RouteMatch {
417 #[must_use] pub fn get_param(&self, key: &str) -> Option<&AzString> {
419 self.params.get_key(key)
420 }
421}
422
423impl_option!(RouteMatch, OptionRouteMatch, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
424
425#[allow(clippy::similar_names)] #[must_use] pub fn match_route(pattern: &str, path: &str) -> Option<RouteMatch> {
435 let pat_segs: Vec<&str> = pattern.split('/').filter(|s| !s.is_empty()).collect();
436 let path_segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
437
438 if pat_segs.len() != path_segs.len() {
439 return None;
440 }
441
442 let mut params = Vec::new();
443 for (pat, val) in pat_segs.iter().zip(path_segs.iter()) {
444 if let Some(param_name) = pat.strip_prefix(':') {
445 params.push(AzStringPair {
446 key: AzString::from(param_name.to_string()),
447 value: AzString::from((*val).to_string()),
448 });
449 } else if pat != val {
450 return None;
451 }
452 }
453
454 Some(RouteMatch {
455 pattern: AzString::from(pattern.to_string()),
456 params: StringPairVec::from_vec(params),
457 })
458}
459
460#[derive(Debug, Clone)]
469#[repr(C)]
470pub struct SystemAnimations {
471 pub caret_tween: crate::callbacks::CaretTweenCallback,
479 pub selection_tween: crate::callbacks::SelectionTweenCallback,
484 pub caret_tween_data: RefAny,
486 pub selection_tween_data: RefAny,
488 pub scroll_physics: OptionScrollPhysics,
491 pub caret_tween_duration_ms: u32,
495 pub selection_tween_duration_ms: u32,
497 pub focus_ring_duration_ms: u32,
504 pub caret_scroll_glide: bool,
508}
509
510impl SystemAnimations {
511 #[must_use] pub fn disabled() -> Self {
515 Self {
516 caret_tween_duration_ms: 0,
517 selection_tween_duration_ms: 0,
518 caret_scroll_glide: false,
519 focus_ring_duration_ms: 0,
520 ..Self::default()
521 }
522 }
523}
524
525impl Default for SystemAnimations {
526 fn default() -> Self {
527 Self {
528 scroll_physics: OptionScrollPhysics::None,
529 caret_tween_duration_ms: 60,
532 caret_tween: crate::callbacks::CaretTweenCallback::create(
533 crate::callbacks::default_caret_tween,
534 ),
535 caret_tween_data: RefAny::new(()),
536 selection_tween_duration_ms: 60,
537 selection_tween: crate::callbacks::SelectionTweenCallback::create(
538 crate::callbacks::default_selection_tween,
539 ),
540 selection_tween_data: RefAny::new(()),
541 caret_scroll_glide: true,
542 focus_ring_duration_ms: 0,
545 }
546 }
547}
548
549#[derive(Debug, Clone)]
551#[repr(C)]
552pub struct AppConfig {
553 pub log_level: AppLogLevel,
557 pub enable_visual_panic_hook: bool,
560 pub enable_logging_on_panic: bool,
563 pub termination_behavior: AppTerminationBehavior,
566 pub icon_provider: crate::icon::IconProviderHandle,
570 pub bundled_fonts: NamedFontVec,
573 pub font_loading: FontLoadingConfig,
576 pub mock_css_environment: OptionCssMockEnvironment,
586 pub system_style: SystemStyle,
592 pub component_libraries: ComponentLibraryVec,
602 pub routes: RouteVec,
609 pub system_animations: SystemAnimations,
612 pub custom_e2e_op: crate::events::CustomE2eOpCallback,
619}
620
621impl AppConfig {
622 #[must_use] pub fn create() -> Self {
623 let log_level = AppLogLevel::Error;
624 let icon_provider = crate::icon::IconProviderHandle::new();
625 let bundled_fonts = NamedFontVec::from_const_slice(&[]);
626 let font_loading = FontLoadingConfig::default();
627 let system_style = SystemStyle::detect();
628 let mut s = Self {
629 log_level,
630 enable_visual_panic_hook: false,
631 enable_logging_on_panic: true,
632 termination_behavior: AppTerminationBehavior::default(),
633 icon_provider,
634 bundled_fonts,
635 font_loading,
636 mock_css_environment: OptionCssMockEnvironment::None,
637 system_style,
638 component_libraries: ComponentLibraryVec::from_const_slice(&[]),
639 routes: RouteVec::from_const_slice(&[]),
640 system_animations: SystemAnimations::default(),
641 custom_e2e_op: crate::events::CustomE2eOpCallback::default(),
642 };
643 let register_builtin: crate::xml::RegisterComponentLibraryFnType =
648 crate::xml::register_builtin_components;
649 s.add_component_library(
650 AzString::from_const_str("builtin"),
651 register_builtin,
652 );
653 s
654 }
655
656 #[must_use] pub fn with_mock_environment(mut self, env: CssMockEnvironment) -> Self {
673 self.mock_css_environment = OptionCssMockEnvironment::Some(env);
674 self
675 }
676
677 pub fn add_component<R: Into<RegisterComponentFn>>(&mut self, library: AzString, register_fn: R) {
689 let register_fn = register_fn.into();
690 let component = (register_fn.cb)();
691 let empty_libs = ComponentLibraryVec::from_const_slice(&[]);
692 let mut libs = core::mem::replace(&mut self.component_libraries, empty_libs).into_library_owned_vec();
693
694 if let Some(existing_lib) = libs.iter_mut().find(|l| l.name.as_str() == library.as_str()) {
695 let empty_comps = ComponentDefVec::from_const_slice(&[]);
696 let mut comps = core::mem::replace(&mut existing_lib.components, empty_comps).into_library_owned_vec();
697 if let Some(ec) = comps.iter_mut().find(|c| c.id.name.as_str() == component.id.name.as_str()) {
698 *ec = component;
699 } else {
700 comps.push(component);
701 }
702 existing_lib.components = ComponentDefVec::from_vec(comps);
703 } else {
704 libs.push(ComponentLibrary {
705 name: library,
706 version: AzString::from_const_str("1.0.0"),
707 description: AzString::from_const_str(""),
708 components: ComponentDefVec::from_vec(alloc::vec![component]),
709 exportable: true,
710 modifiable: true,
711 data_models: crate::xml::ComponentDataModelVec::from_const_slice(&[]),
712 enum_models: crate::xml::ComponentEnumModelVec::from_const_slice(&[]),
713 });
714 }
715
716 self.component_libraries = ComponentLibraryVec::from_vec(libs);
717 }
718
719 pub fn add_component_library<R: Into<RegisterComponentLibraryFn>>(&mut self, name: AzString, register_fn: R) {
731 let register_fn = register_fn.into();
732 let mut library = (register_fn.cb)();
733 library.name = name;
734
735 let empty_libs = ComponentLibraryVec::from_const_slice(&[]);
736 let mut libs = core::mem::replace(&mut self.component_libraries, empty_libs).into_library_owned_vec();
737 if let Some(existing) = libs.iter_mut().find(|l| l.name.as_str() == library.name.as_str()) {
738 *existing = library;
739 } else {
740 libs.push(library);
741 }
742
743 self.component_libraries = ComponentLibraryVec::from_vec(libs);
744 }
745
746 pub fn add_route<P: Into<AzString>, L: Into<LayoutCallback>>(&mut self, pattern: P, layout_fn: L) {
757 let route = Route {
758 pattern: pattern.into(),
759 layout_callback: layout_fn.into(),
760 };
761 let empty = RouteVec::from_const_slice(&[]);
762 let mut routes = core::mem::replace(&mut self.routes, empty).into_library_owned_vec();
763 if let Some(existing) = routes.iter_mut().find(|r| r.pattern.as_str() == route.pattern.as_str()) {
765 *existing = route;
766 } else {
767 routes.push(route);
768 }
769 self.routes = RouteVec::from_vec(routes);
770 }
771
772 #[must_use] pub fn match_route_for_path(&self, path: &str) -> Option<(&Route, RouteMatch)> {
776 for route in self.routes.as_ref() {
777 if let Some(m) = match_route(route.pattern.as_str(), path) {
778 return Some((route, m));
779 }
780 }
781 None
782 }
783}
784
785impl Default for AppConfig {
786 fn default() -> Self {
787 Self::create()
788 }
789}
790
791#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
792#[repr(C)]
793pub enum AppLogLevel {
794 Off,
795 Error,
796 Warn,
797 Info,
798 Debug,
799 Trace,
800}
801
802#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
804#[repr(C)]
805pub struct ImageDescriptor {
806 pub format: RawImageFormat,
808 pub width: usize,
810 pub height: usize,
811 pub stride: OptionI32,
816 pub offset: i32,
822 pub flags: ImageDescriptorFlags,
824}
825
826#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
828#[repr(C)]
829pub struct ImageDescriptorFlags {
830 pub is_opaque: bool,
833 pub allow_mipmaps: bool,
839}
840
841#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
842pub struct IdNamespace(pub u32);
843
844impl ::core::fmt::Display for IdNamespace {
845 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
846 write!(f, "IdNamespace({})", self.0)
847 }
848}
849
850impl ::core::fmt::Debug for IdNamespace {
851 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
852 write!(f, "{self}")
853 }
854}
855
856#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
857#[repr(C)]
858pub enum RawImageFormat {
859 R8,
860 RG8,
861 RGB8,
862 RGBA8,
863 R16,
864 RG16,
865 RGB16,
866 RGBA16,
867 BGR8,
868 BGRA8,
869 RGBF32,
870 RGBAF32,
871}
872
873static IMAGE_KEY: AtomicU64 = AtomicU64::new(1);
875static FONT_KEY: AtomicU64 = AtomicU64::new(0);
876static FONT_INSTANCE_KEY: AtomicU64 = AtomicU64::new(0);
877
878#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
879pub struct ImageKey {
880 pub namespace: IdNamespace,
881 pub key: u64,
882}
883
884impl ImageKey {
885 pub const DUMMY: Self = Self {
886 namespace: IdNamespace(0),
887 key: 0,
888 };
889
890 pub fn unique(render_api_namespace: IdNamespace) -> Self {
891 Self {
892 namespace: render_api_namespace,
893 key: IMAGE_KEY.fetch_add(1, AtomicOrdering::SeqCst),
894 }
895 }
896}
897
898#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
899pub struct FontKey {
900 pub namespace: IdNamespace,
901 pub key: u64,
902}
903
904impl FontKey {
905 pub fn unique(render_api_namespace: IdNamespace) -> Self {
906 Self {
907 namespace: render_api_namespace,
908 key: FONT_KEY.fetch_add(1, AtomicOrdering::SeqCst),
909 }
910 }
911}
912
913#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
914pub struct FontInstanceKey {
915 pub namespace: IdNamespace,
916 pub key: u64,
917}
918
919impl FontInstanceKey {
920 pub fn unique(render_api_namespace: IdNamespace) -> Self {
921 Self {
922 namespace: render_api_namespace,
923 key: FONT_INSTANCE_KEY.fetch_add(1, AtomicOrdering::SeqCst),
924 }
925 }
926}
927
928#[derive(Debug)]
931pub enum DecodedImage {
932 NullImage {
935 width: usize,
936 height: usize,
937 format: RawImageFormat,
938 tag: Vec<u8>,
940 },
941 Gl(Texture),
943 Raw((ImageDescriptor, ImageData)),
945 Callback(CoreImageCallback),
947 }
952
953#[derive(Debug)]
954#[repr(C)]
955pub struct ImageRef {
956 pub data: *const DecodedImage,
958 pub copies: *const AtomicUsize,
960 pub id: u64,
968 pub run_destructor: bool,
969}
970
971static IMAGE_REF_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
974
975#[must_use]
976fn next_image_ref_id() -> u64 {
977 IMAGE_REF_ID_COUNTER.fetch_add(1, AtomicOrdering::SeqCst)
978}
979
980impl ImageRef {
981 #[must_use] pub const fn get_hash(&self) -> ImageRefHash {
982 image_ref_get_hash(self)
983 }
984}
985
986#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Ord, Eq)]
987#[repr(C)]
988pub struct ImageRefHash {
989 pub inner: u64,
990}
991
992impl_option!(
993 ImageRef,
994 OptionImageRef,
995 copy = false,
996 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
997);
998
999impl ImageRef {
1000 #[must_use] pub fn into_inner(self) -> Option<DecodedImage> {
1002 unsafe {
1007 if self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) == Some(1) {
1008 let data = Box::from_raw(self.data.cast_mut());
1009 drop(Box::from_raw(self.copies.cast_mut()));
1010 core::mem::forget(self); Some(*data)
1012 } else {
1013 None
1014 }
1015 }
1016 }
1017
1018 #[must_use] pub const fn get_data(&self) -> &DecodedImage {
1019 unsafe { &*self.data }
1023 }
1024
1025 #[must_use] pub fn get_image_callback(&self) -> Option<&CoreImageCallback> {
1026 if unsafe { self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) != Some(1) } {
1028 return None; }
1030
1031 match unsafe { &*self.data } {
1033 DecodedImage::Callback(gl_texture_callback) => Some(gl_texture_callback),
1034 _ => None,
1035 }
1036 }
1037
1038 pub fn get_image_callback_mut(&mut self) -> Option<&mut CoreImageCallback> {
1039 if unsafe { self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) != Some(1) } {
1041 return None; }
1043
1044 match unsafe { &mut *self.data.cast_mut() } {
1047 DecodedImage::Callback(gl_texture_callback) => Some(gl_texture_callback),
1048 _ => None,
1049 }
1050 }
1051
1052 #[must_use] pub fn deep_copy(&self) -> Self {
1054 let new_data = match self.get_data() {
1055 DecodedImage::NullImage {
1056 width,
1057 height,
1058 format,
1059 tag,
1060 } => DecodedImage::NullImage {
1061 width: *width,
1062 height: *height,
1063 format: *format,
1064 tag: tag.clone(),
1065 },
1066 DecodedImage::Gl(tex) => DecodedImage::NullImage {
1070 width: tex.size.width as usize,
1071 height: tex.size.height as usize,
1072 format: tex.format,
1073 tag: Vec::new(),
1074 },
1075 DecodedImage::Raw((descriptor, data)) => {
1078 DecodedImage::Raw((*descriptor, data.clone()))
1079 }
1080 DecodedImage::Callback(cb) => DecodedImage::Callback(cb.clone()),
1081 };
1082
1083 Self::new(new_data)
1084 }
1085
1086 #[must_use] pub const fn is_null_image(&self) -> bool {
1087 matches!(self.get_data(), DecodedImage::NullImage { .. })
1088 }
1089
1090 #[must_use] pub const fn is_gl_texture(&self) -> bool {
1091 matches!(self.get_data(), DecodedImage::Gl(_))
1092 }
1093
1094 #[must_use] pub const fn is_raw_image(&self) -> bool {
1095 matches!(self.get_data(), DecodedImage::Raw((_, _)))
1096 }
1097
1098 #[must_use] pub const fn is_callback(&self) -> bool {
1099 matches!(self.get_data(), DecodedImage::Callback(_))
1100 }
1101
1102 #[must_use] pub fn get_rawimage(&self) -> Option<RawImage> {
1104 match self.get_data() {
1105 DecodedImage::Raw((image_descriptor, image_data)) => Some(RawImage {
1106 pixels: match image_data {
1107 ImageData::Raw(shared_data) => {
1108 let data_clone = shared_data.clone();
1111 data_clone.into_inner().map_or_else(|| RawImageData::U8(shared_data.as_ref().to_vec().into()), RawImageData::U8)
1112 }
1113 ImageData::External(_) => return None,
1114 },
1115 width: image_descriptor.width,
1116 height: image_descriptor.height,
1117 premultiplied_alpha: true,
1118 data_format: image_descriptor.format,
1119 tag: Vec::new().into(),
1120 }),
1121 _ => None,
1122 }
1123 }
1124
1125 #[must_use] pub fn get_bytes(&self) -> Option<&[u8]> {
1128 match self.get_data() {
1129 DecodedImage::Raw((_, image_data)) => match image_data {
1130 ImageData::Raw(shared_data) => Some(shared_data.as_ref()),
1131 ImageData::External(_) => None,
1132 },
1133 _ => None,
1134 }
1135 }
1136
1137 #[must_use] pub fn get_bytes_ptr(&self) -> *const u8 {
1140 match self.get_data() {
1141 DecodedImage::Raw((_, image_data)) => match image_data {
1142 ImageData::Raw(shared_data) => shared_data.as_ptr(),
1143 ImageData::External(_) => core::ptr::null(),
1144 },
1145 _ => core::ptr::null(),
1146 }
1147 }
1148
1149 #[allow(clippy::cast_precision_loss)] #[must_use] pub const fn get_size(&self) -> LogicalSize {
1152 match self.get_data() {
1153 DecodedImage::NullImage { width, height, .. } => {
1154 LogicalSize::new(*width as f32, *height as f32)
1155 }
1156 DecodedImage::Gl(tex) => {
1157 LogicalSize::new(tex.size.width as f32, tex.size.height as f32)
1158 }
1159 DecodedImage::Raw((image_descriptor, _)) => LogicalSize::new(
1160 image_descriptor.width as f32,
1161 image_descriptor.height as f32,
1162 ),
1163 DecodedImage::Callback(_) => LogicalSize::new(0.0, 0.0),
1164 }
1165 }
1166
1167 #[must_use] pub fn null_image(width: usize, height: usize, format: RawImageFormat, tag: Vec<u8>) -> Self {
1168 Self::new(DecodedImage::NullImage {
1169 width,
1170 height,
1171 format,
1172 tag,
1173 })
1174 }
1175
1176 pub fn callback<C: Into<CoreRenderImageCallback>>(callback: C, data: RefAny) -> Self {
1177 Self::new(DecodedImage::Callback(CoreImageCallback {
1178 callback: callback.into(),
1179 refany: data,
1180 }))
1181 }
1182
1183 #[must_use] pub fn new_rawimage(image_data: RawImage) -> Option<Self> {
1184 let (image_data, image_descriptor) = image_data.into_loaded_image_source()?;
1185 Some(Self::new(DecodedImage::Raw((image_descriptor, image_data))))
1186 }
1187
1188 #[must_use] pub fn new_gltexture(texture: Texture) -> Self {
1189 Self::new(DecodedImage::Gl(texture))
1190 }
1191
1192 fn new(data: DecodedImage) -> Self {
1193 Self {
1194 data: Box::into_raw(Box::new(data)),
1195 copies: Box::into_raw(Box::new(AtomicUsize::new(1))),
1196 id: next_image_ref_id(),
1197 run_destructor: true,
1198 }
1199 }
1200
1201 }
1203
1204unsafe impl Send for ImageRef {}
1208unsafe impl Sync for ImageRef {}
1209
1210impl PartialEq for ImageRef {
1215 fn eq(&self, rhs: &Self) -> bool {
1216 self.id == rhs.id
1217 }
1218}
1219
1220impl PartialOrd for ImageRef {
1221 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
1222 Some(self.id.cmp(&other.id))
1223 }
1224}
1225
1226impl Ord for ImageRef {
1227 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1228 self.id.cmp(&other.id)
1229 }
1230}
1231
1232impl Eq for ImageRef {}
1233
1234impl Hash for ImageRef {
1235 fn hash<H>(&self, state: &mut H)
1236 where
1237 H: Hasher,
1238 {
1239 self.id.hash(state);
1240 }
1241}
1242
1243impl Clone for ImageRef {
1244 fn clone(&self) -> Self {
1245 unsafe {
1248 self.copies
1249 .as_ref()
1250 .map(|m| m.fetch_add(1, AtomicOrdering::SeqCst));
1251 }
1252 Self {
1253 data: self.data, copies: self.copies, id: self.id, run_destructor: true,
1257 }
1258 }
1259}
1260
1261impl Drop for ImageRef {
1262 fn drop(&mut self) {
1263 self.run_destructor = false;
1264 unsafe {
1268 let copies = (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst);
1269 if copies == 1 {
1270 drop(Box::from_raw(self.data.cast_mut()));
1271 drop(Box::from_raw(self.copies.cast_mut()));
1272 }
1273 }
1274 }
1275}
1276
1277#[must_use] pub const fn image_ref_get_hash(ir: &ImageRef) -> ImageRefHash {
1278 ImageRefHash {
1284 inner: ir.id,
1285 }
1286}
1287
1288#[must_use] pub const fn image_ref_hash_to_image_key(hash: ImageRefHash, namespace: IdNamespace) -> ImageKey {
1295 ImageKey {
1296 namespace,
1297 key: hash.inner,
1298 }
1299}
1300
1301#[must_use] pub fn font_ref_get_hash(fr: &FontRef) -> u64 {
1302 fr.get_hash()
1303}
1304
1305#[derive(Debug)]
1311#[derive(Default)]
1312pub struct ImageCache {
1313 pub image_id_map: OrderedMap<AzString, ImageRef>,
1319}
1320
1321
1322impl ImageCache {
1323 #[must_use] pub fn new() -> Self {
1324 Self::default()
1325 }
1326
1327 pub fn add_css_image_id(&mut self, css_id: AzString, image: ImageRef) {
1330 self.image_id_map.insert(css_id, image);
1331 }
1332
1333 #[must_use] pub fn get_css_image_id(&self, css_id: &AzString) -> Option<&ImageRef> {
1334 self.image_id_map.get(css_id)
1335 }
1336
1337 pub fn delete_css_image_id(&mut self, css_id: &AzString) {
1338 self.image_id_map.remove(css_id);
1339 }
1340}
1341
1342#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1343pub struct ResolvedImage {
1344 pub key: ImageKey,
1345 pub descriptor: ImageDescriptor,
1346}
1347
1348pub trait RendererResourcesTrait: fmt::Debug {
1350 fn get_font_family(
1352 &self,
1353 style_font_families_hash: &StyleFontFamiliesHash,
1354 ) -> Option<&StyleFontFamilyHash>;
1355
1356 fn get_font_key(&self, style_font_family_hash: &StyleFontFamilyHash) -> Option<&FontKey>;
1358
1359 fn get_registered_font(
1361 &self,
1362 font_key: &FontKey,
1363 ) -> Option<&(FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)>;
1364
1365 fn get_image(&self, hash: &ImageRefHash) -> Option<&ResolvedImage>;
1367
1368 fn update_image(
1370 &mut self,
1371 image_ref_hash: &ImageRefHash,
1372 descriptor: ImageDescriptor,
1373 );
1374}
1375
1376impl RendererResourcesTrait for RendererResources {
1378 fn get_font_family(
1379 &self,
1380 style_font_families_hash: &StyleFontFamiliesHash,
1381 ) -> Option<&StyleFontFamilyHash> {
1382 self.font_families_map.get(style_font_families_hash)
1383 }
1384
1385 fn get_font_key(&self, style_font_family_hash: &StyleFontFamilyHash) -> Option<&FontKey> {
1386 self.font_id_map.get(style_font_family_hash)
1387 }
1388
1389 fn get_registered_font(
1390 &self,
1391 font_key: &FontKey,
1392 ) -> Option<&(FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)> {
1393 self.currently_registered_fonts.get(font_key)
1394 }
1395
1396 fn get_image(&self, hash: &ImageRefHash) -> Option<&ResolvedImage> {
1397 self.currently_registered_images.get(hash)
1398 }
1399
1400 fn update_image(
1401 &mut self,
1402 image_ref_hash: &ImageRefHash,
1403 descriptor: ImageDescriptor,
1404 ) {
1405 if let Some(s) = self.currently_registered_images.get_mut(image_ref_hash) {
1406 s.descriptor = descriptor;
1407 }
1408 }
1409}
1410
1411#[derive(Default)]
1418pub struct RendererResources {
1419 pub currently_registered_images: OrderedMap<ImageRefHash, ResolvedImage>,
1421 pub image_key_map: OrderedMap<ImageKey, ImageRefHash>,
1423 pub image_last_seen_epoch: OrderedMap<ImageRefHash, u32>,
1430 pub currently_registered_fonts:
1432 OrderedMap<FontKey, (FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)>,
1433 pub last_frame_registered_fonts:
1440 OrderedMap<FontKey, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>>,
1441 pub font_families_map: OrderedMap<StyleFontFamiliesHash, StyleFontFamilyHash>,
1446 pub font_id_map: OrderedMap<StyleFontFamilyHash, FontKey>,
1448 pub font_hash_map: OrderedMap<u64, FontKey>,
1451}
1452
1453impl fmt::Debug for RendererResources {
1454 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1455 write!(
1456 f,
1457 "RendererResources {{
1458 currently_registered_images: {:#?},
1459 currently_registered_fonts: {:#?},
1460 font_families_map: {:#?},
1461 font_id_map: {:#?},
1462 }}",
1463 self.currently_registered_images.keys().collect::<Vec<_>>(),
1464 self.currently_registered_fonts.keys().collect::<Vec<_>>(),
1465 self.font_families_map.keys().collect::<Vec<_>>(),
1466 self.font_id_map.keys().collect::<Vec<_>>(),
1467 )
1468 }
1469}
1470
1471
1472impl RendererResources {
1473 #[must_use] pub fn get_renderable_font_data(
1474 &self,
1475 font_instance_key: &FontInstanceKey,
1476 ) -> Option<(&FontRef, Au, DpiScaleFactor)> {
1477 self.currently_registered_fonts
1478 .iter()
1479 .find_map(|(font_key, (font_ref, instances))| {
1480 instances.iter().find_map(|((au, dpi), instance_key)| {
1481 if *instance_key == *font_instance_key {
1482 Some((font_ref, *au, *dpi))
1483 } else {
1484 None
1485 }
1486 })
1487 })
1488 }
1489
1490 #[allow(clippy::cast_possible_truncation)] pub fn get_font_instance_key_for_text(
1492 &self,
1493 font_size_px: f32,
1494 css_property_cache: &CssPropertyCache,
1495 node_data: &NodeData,
1496 node_id: &NodeId,
1497 styled_node_state: &StyledNodeState,
1498 dpi_scale: f32,
1499 ) -> Option<FontInstanceKey> {
1500 let font_size_isize =
1508 (font_size_px as isize).clamp(isize::MIN / 1000, isize::MAX / 1000);
1509 let font_size = StyleFontSize {
1510 inner: azul_css::props::basic::PixelValue::const_px(font_size_isize),
1511 };
1512
1513 let font_size_au = font_size_to_au(font_size);
1515
1516 let dpi_scale_factor = DpiScaleFactor {
1518 inner: FloatValue::new(dpi_scale),
1519 };
1520
1521 let font_family =
1523 css_property_cache.get_font_id_or_default(node_data, node_id, styled_node_state);
1524
1525 let font_families_hash = StyleFontFamiliesHash::new(font_family.as_ref());
1527
1528 self.get_font_instance_key(&font_families_hash, font_size_au, dpi_scale_factor)
1529 }
1530
1531 #[must_use] pub fn get_font_instance_key(
1532 &self,
1533 font_families_hash: &StyleFontFamiliesHash,
1534 font_size_au: Au,
1535 dpi_scale: DpiScaleFactor,
1536 ) -> Option<FontInstanceKey> {
1537 let font_family_hash = self.get_font_family(font_families_hash)?;
1538 let font_key = self.get_font_key(font_family_hash)?;
1539 let (_, instances) = self.get_registered_font(font_key)?;
1540 instances.get(&(font_size_au, dpi_scale)).copied()
1541 }
1542
1543 #[allow(dead_code)]
1574 fn remove_font_families_with_zero_references(&mut self) {
1575 let font_family_to_delete = self
1576 .font_id_map
1577 .iter()
1578 .filter_map(|(font_family, font_key)| {
1579 if self.currently_registered_fonts.contains_key(font_key) {
1580 None
1581 } else {
1582 Some(*font_family)
1583 }
1584 })
1585 .collect::<Vec<_>>();
1586
1587 for f in font_family_to_delete {
1588 self.font_id_map.remove(&f); }
1590
1591 let font_families_to_delete = self
1592 .font_families_map
1593 .iter()
1594 .filter_map(|(font_families, font_family)| {
1595 if self.font_id_map.contains_key(font_family) {
1596 None
1597 } else {
1598 Some(*font_families)
1599 }
1600 })
1601 .collect::<Vec<_>>();
1602
1603 for f in font_families_to_delete {
1604 self.font_families_map.remove(&f); }
1606 }
1607}
1608
1609#[derive(Debug, Clone)]
1620pub struct UpdateImageResult {
1621 pub key_to_update: ImageKey,
1622 pub new_descriptor: ImageDescriptor,
1623 pub new_image_data: ImageData,
1624}
1625
1626#[derive(Debug, Default)]
1627pub struct GlTextureCache {
1628 pub solved_textures:
1629 BTreeMap<DomId, BTreeMap<NodeId, (ImageKey, ImageDescriptor, ExternalImageId)>>,
1630 pub hashes: BTreeMap<(DomId, NodeId, ImageRefHash), ImageRefHash>,
1631}
1632
1633unsafe impl Send for GlTextureCache {}
1638
1639impl GlTextureCache {
1640 #[must_use] pub const fn empty() -> Self {
1642 Self {
1643 solved_textures: BTreeMap::new(),
1644 hashes: BTreeMap::new(),
1645 }
1646 }
1647
1648 pub fn update_texture(
1667 &mut self,
1668 dom_id: DomId,
1669 node_id: NodeId,
1670 document_id: DocumentId,
1671 epoch: Epoch,
1672 new_texture: Texture,
1673 insert_into_active_gl_textures_fn: &GlStoreImageFn,
1674 ) -> Option<ExternalImageId> {
1675 let new_descriptor = new_texture.get_descriptor();
1676 let di_map = self.solved_textures.get_mut(&dom_id)?;
1677 let entry = di_map.get_mut(&node_id)?;
1678
1679 entry.1 = new_descriptor;
1681
1682 let external_image_id = texture_external_image_id(dom_id, node_id);
1685 (insert_into_active_gl_textures_fn)(document_id, epoch, new_texture, external_image_id);
1686 entry.2 = external_image_id;
1687
1688 Some(external_image_id)
1689 }
1690}
1691
1692macro_rules! unique_id {
1693 ($struct_name:ident, $counter_name:ident) => {
1694 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
1695 #[repr(C)]
1696 pub struct $struct_name {
1697 pub id: usize,
1698 }
1699
1700 impl $struct_name {
1701 pub fn unique() -> Self {
1702 Self {
1703 id: $counter_name.fetch_add(1, AtomicOrdering::SeqCst),
1704 }
1705 }
1706 }
1707 };
1708}
1709
1710static PROPERTY_KEY_COUNTER: AtomicUsize = AtomicUsize::new(0);
1712unique_id!(TransformKey, PROPERTY_KEY_COUNTER);
1713unique_id!(ColorKey, PROPERTY_KEY_COUNTER);
1714unique_id!(OpacityKey, PROPERTY_KEY_COUNTER);
1715
1716static IMAGE_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
1717unique_id!(ImageId, IMAGE_ID_COUNTER);
1718static FONT_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
1719unique_id!(FontId, FONT_ID_COUNTER);
1720
1721#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1722#[repr(C)]
1723pub struct ImageMask {
1724 pub image: ImageRef,
1725 pub rect: LogicalRect,
1726 pub repeat: bool,
1727}
1728
1729impl_option!(
1730 ImageMask,
1731 OptionImageMask,
1732 copy = false,
1733 [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
1734);
1735
1736#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1737pub enum ImmediateFontId {
1738 Resolved((StyleFontFamilyHash, FontKey)),
1739 Unresolved(StyleFontFamilyVec),
1740}
1741
1742#[derive(Debug, Clone, PartialEq, PartialOrd)]
1743#[repr(C, u8)]
1744pub enum RawImageData {
1745 U8(U8Vec),
1747 U16(U16Vec),
1749 F32(F32Vec),
1751}
1752
1753impl RawImageData {
1754 #[must_use] pub const fn get_u8_vec_ref(&self) -> Option<&U8Vec> {
1755 match self {
1756 Self::U8(v) => Some(v),
1757 _ => None,
1758 }
1759 }
1760
1761 #[must_use] pub const fn get_u16_vec_ref(&self) -> Option<&U16Vec> {
1762 match self {
1763 Self::U16(v) => Some(v),
1764 _ => None,
1765 }
1766 }
1767
1768 #[must_use] pub const fn get_f32_vec_ref(&self) -> Option<&F32Vec> {
1769 match self {
1770 Self::F32(v) => Some(v),
1771 _ => None,
1772 }
1773 }
1774
1775 fn get_u8_vec(self) -> Option<U8Vec> {
1776 match self {
1777 Self::U8(v) => Some(v),
1778 _ => None,
1779 }
1780 }
1781
1782 fn get_u16_vec(self) -> Option<U16Vec> {
1783 match self {
1784 Self::U16(v) => Some(v),
1785 _ => None,
1786 }
1787 }
1788}
1789
1790#[derive(Debug, Clone, PartialEq, PartialOrd)]
1791#[repr(C)]
1792pub struct RawImage {
1793 pub pixels: RawImageData,
1794 pub width: usize,
1795 pub height: usize,
1796 pub premultiplied_alpha: bool,
1797 pub data_format: RawImageFormat,
1798 pub tag: U8Vec,
1799}
1800
1801#[repr(C)]
1807#[derive(Debug, Copy, Clone, PartialEq)]
1808pub struct Brush {
1809 pub color: ColorU,
1811 pub radius: f32,
1813 pub hardness: f32,
1816 pub flow: f32,
1819 pub spacing: f32,
1822}
1823
1824impl Brush {
1825 #[must_use] pub const fn new(color: ColorU, radius: f32) -> Self {
1827 Self {
1828 color,
1829 radius,
1830 hardness: 0.5,
1831 flow: 1.0,
1832 spacing: 0.25,
1833 }
1834 }
1835}
1836
1837#[allow(clippy::suboptimal_flops)] #[inline]
1844#[must_use] pub fn brush_dab_coverage(t: f32, hardness: f32) -> f32 {
1845 let edge0 = hardness.clamp(0.0, 1.0);
1846 let denom = (1.0 - edge0).max(1.0e-4);
1847 let x = ((t - edge0) / denom).clamp(0.0, 1.0);
1848 1.0 - (x * x * (3.0 - 2.0 * x))
1849}
1850
1851impl RawImage {
1852 #[allow(clippy::suboptimal_flops)] #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss, clippy::cast_sign_loss)] #[allow(clippy::cast_possible_wrap)] pub fn paint_dot(&mut self, cx: f32, cy: f32, brush: Brush) {
1860 let r = brush.radius;
1861 #[allow(clippy::neg_cmp_op_on_partial_ord)]
1863 if !(r > 0.0) || self.width == 0 || self.height == 0 {
1864 return;
1865 }
1866 let bgr = match self.data_format {
1867 RawImageFormat::RGBA8 => false,
1868 RawImageFormat::BGRA8 => true,
1869 _ => return,
1870 };
1871 let (w, h) = (self.width as i32, self.height as i32);
1872 let buf: &mut [u8] = match self.pixels {
1873 RawImageData::U8(ref mut v) => v.as_mut(),
1874 _ => return,
1875 };
1876 let flow = brush.flow.clamp(0.0, 1.0) * (f32::from(brush.color.a) / 255.0);
1877 let (cr, cg, cb) = (
1878 f32::from(brush.color.r),
1879 f32::from(brush.color.g),
1880 f32::from(brush.color.b),
1881 );
1882 let x0 = (cx - r).floor().max(0.0) as i32;
1883 let y0 = (cy - r).floor().max(0.0) as i32;
1884 let x1 = ((cx + r).ceil() as i32).min(w);
1885 let y1 = ((cy + r).ceil() as i32).min(h);
1886 for y in y0..y1 {
1887 for x in x0..x1 {
1888 let dx = x as f32 + 0.5 - cx;
1889 let dy = y as f32 + 0.5 - cy;
1890 let dist = dx.hypot(dy);
1891 if dist > r {
1892 continue;
1893 }
1894 let a = brush_dab_coverage(dist / r, brush.hardness) * flow;
1895 if a <= 0.0 {
1896 continue;
1897 }
1898 let idx = ((y * w + x) as usize) * 4;
1899 if idx + 4 > buf.len() {
1903 continue;
1904 }
1905 let (ri, gi, bi, ai) = if bgr {
1906 (idx + 2, idx + 1, idx, idx + 3)
1907 } else {
1908 (idx, idx + 1, idx + 2, idx + 3)
1909 };
1910 let inv = 1.0 - a;
1911 buf[ri] = (cr * a + f32::from(buf[ri]) * inv).round().clamp(0.0, 255.0) as u8;
1912 buf[gi] = (cg * a + f32::from(buf[gi]) * inv).round().clamp(0.0, 255.0) as u8;
1913 buf[bi] = (cb * a + f32::from(buf[bi]) * inv).round().clamp(0.0, 255.0) as u8;
1914 buf[ai] =
1915 ((a + (f32::from(buf[ai]) / 255.0) * inv) * 255.0).round().clamp(0.0, 255.0) as u8;
1916 }
1917 }
1918 }
1919
1920 #[allow(clippy::suboptimal_flops)] #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] pub fn paint_stroke(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, brush: Brush) {
1926 let dx = x1 - x0;
1927 let dy = y1 - y0;
1928 let len = dx.hypot(dy);
1929 if !len.is_finite() {
1933 return;
1934 }
1935 let step = (brush.radius * brush.spacing.max(0.01)).max(0.5);
1936 let n = (len / step).floor() as i32;
1937 if n <= 0 {
1938 self.paint_dot(x1, y1, brush);
1939 return;
1940 }
1941 for i in 0..=n {
1942 let t = i as f32 / n as f32;
1943 self.paint_dot(x0 + dx * t, y0 + dy * t, brush);
1944 }
1945 }
1946}
1947
1948#[inline]
1953#[allow(clippy::cast_possible_truncation)] fn premultiply_alpha(array: &mut [u8]) {
1955 if array.len() != 4 {
1956 return;
1957 }
1958 let a = u32::from(array[3]);
1959 array[0] = (((u32::from(array[0]) * a) + 128) / 255) as u8;
1960 array[1] = (((u32::from(array[1]) * a) + 128) / 255) as u8;
1961 array[2] = (((u32::from(array[2]) * a) + 128) / 255) as u8;
1962}
1963
1964#[inline]
1965#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] fn normalize_u16(i: u16) -> u8 {
1968 ((f32::from(i) / f32::from(core::u16::MAX)) * f32::from(core::u8::MAX)) as u8
1969}
1970
1971const FOUR_BPP: usize = 4;
1972const TWO_CHANNELS: usize = 2;
1973const THREE_CHANNELS: usize = 3;
1974const FOUR_CHANNELS: usize = 4;
1975
1976impl RawImage {
1977 #[must_use] pub fn null_image() -> Self {
1979 Self {
1980 pixels: RawImageData::U8(Vec::new().into()),
1981 width: 0,
1982 height: 0,
1983 premultiplied_alpha: true,
1984 data_format: RawImageFormat::BGRA8,
1985 tag: Vec::new().into(),
1986 }
1987 }
1988
1989 #[allow(clippy::cast_sign_loss)] #[must_use] pub fn allocate_mask(size: LayoutSize) -> Self {
1992 Self {
1993 pixels: RawImageData::U8(
1994 vec![0; size.width.max(0) as usize * size.height.max(0) as usize].into(),
1995 ),
1996 width: size.width as usize,
1997 height: size.height as usize,
1998 premultiplied_alpha: true,
1999 data_format: RawImageFormat::R8,
2000 tag: Vec::new().into(),
2001 }
2002 }
2003
2004 #[must_use] pub fn into_loaded_image_source(self) -> Option<(ImageData, ImageDescriptor)> {
2010 let Self {
2011 width,
2012 height,
2013 pixels,
2014 data_format,
2015 premultiplied_alpha,
2016 tag,
2017 } = self;
2018
2019 let expected_len = width.checked_mul(height)?;
2022
2023 expected_len.checked_mul(FOUR_BPP)?;
2033
2034 let (bytes, data_format, is_opaque): (U8Vec, RawImageFormat, bool) = match data_format {
2035 RawImageFormat::R8 => {
2036 let (bytes, is_opaque) = Self::load_r8(pixels, expected_len)?;
2037 (bytes, RawImageFormat::R8, is_opaque)
2038 }
2039 RawImageFormat::RG8 => {
2040 let (bytes, is_opaque) = Self::load_rg8(pixels, expected_len, premultiplied_alpha)?;
2041 (bytes, RawImageFormat::BGRA8, is_opaque)
2042 }
2043 RawImageFormat::RGB8 => {
2044 let (bytes, is_opaque) = Self::load_rgb8(pixels, expected_len)?;
2045 (bytes, RawImageFormat::BGRA8, is_opaque)
2046 }
2047 RawImageFormat::RGBA8 => {
2048 let (bytes, is_opaque) = Self::load_rgba8(pixels, expected_len, premultiplied_alpha)?;
2049 (bytes, RawImageFormat::BGRA8, is_opaque)
2050 }
2051 RawImageFormat::R16 => {
2052 let (bytes, is_opaque) = Self::load_r16(pixels, expected_len)?;
2053 (bytes, RawImageFormat::BGRA8, is_opaque)
2054 }
2055 RawImageFormat::RG16 => {
2056 let (bytes, is_opaque) = Self::load_rg16(pixels, expected_len)?;
2057 (bytes, RawImageFormat::BGRA8, is_opaque)
2058 }
2059 RawImageFormat::RGB16 => {
2060 let (bytes, is_opaque) = Self::load_rgb16(pixels, expected_len)?;
2061 (bytes, RawImageFormat::BGRA8, is_opaque)
2062 }
2063 RawImageFormat::RGBA16 => {
2064 let (bytes, is_opaque) =
2065 Self::load_rgba16(pixels, expected_len, premultiplied_alpha)?;
2066 (bytes, RawImageFormat::BGRA8, is_opaque)
2067 }
2068 RawImageFormat::BGR8 => {
2069 let (bytes, is_opaque) = Self::load_bgr8(pixels, expected_len)?;
2070 (bytes, RawImageFormat::BGRA8, is_opaque)
2071 }
2072 RawImageFormat::BGRA8 => {
2073 let (bytes, is_opaque) = Self::load_bgra8(pixels, expected_len, premultiplied_alpha)?;
2074 (bytes, RawImageFormat::BGRA8, is_opaque)
2075 }
2076 RawImageFormat::RGBF32 => {
2077 let (bytes, is_opaque) = Self::load_rgbf32(pixels, expected_len)?;
2078 (bytes, RawImageFormat::BGRA8, is_opaque)
2079 }
2080 RawImageFormat::RGBAF32 => {
2081 let (bytes, is_opaque) =
2082 Self::load_rgbaf32(pixels, expected_len, premultiplied_alpha)?;
2083 (bytes, RawImageFormat::BGRA8, is_opaque)
2084 }
2085 };
2086
2087 let image_data = ImageData::Raw(SharedRawImageData::new(bytes));
2088 let image_descriptor = ImageDescriptor {
2089 format: data_format,
2090 width,
2091 height,
2092 offset: 0,
2093 stride: None.into(),
2094 flags: ImageDescriptorFlags {
2095 is_opaque,
2096 allow_mipmaps: true,
2097 },
2098 };
2099
2100 Some((image_data, image_descriptor))
2101 }
2102
2103 fn load_r8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2107 let pixels = pixels.get_u8_vec()?;
2108
2109 if pixels.len() != expected_len {
2110 return None;
2111 }
2112
2113 Some((pixels, false))
2114 }
2115
2116 fn load_rg8(
2117 pixels: RawImageData,
2118 expected_len: usize,
2119 premultiplied_alpha: bool,
2120 ) -> Option<(U8Vec, bool)> {
2121 let pixels = pixels.get_u8_vec()?;
2122
2123 if pixels.len() != expected_len * TWO_CHANNELS {
2124 return None;
2125 }
2126
2127 let mut is_opaque = true;
2128 let mut px = vec![0; expected_len * FOUR_BPP];
2129
2130 for (pixel_index, greyalpha) in pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate() {
2132 let grey = greyalpha[0];
2133 let alpha = greyalpha[1];
2134
2135 if alpha != 255 {
2136 is_opaque = false;
2137 }
2138
2139 px[pixel_index * FOUR_BPP] = grey;
2140 px[(pixel_index * FOUR_BPP) + 1] = grey;
2141 px[(pixel_index * FOUR_BPP) + 2] = grey;
2142 px[(pixel_index * FOUR_BPP) + 3] = alpha;
2143
2144 if !premultiplied_alpha {
2145 premultiply_alpha(
2146 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2147 );
2148 }
2149 }
2150
2151 Some((px.into(), is_opaque))
2152 }
2153
2154 fn load_rgb8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2155 let pixels = pixels.get_u8_vec()?;
2156
2157 if pixels.len() != expected_len * THREE_CHANNELS {
2158 return None;
2159 }
2160
2161 let mut px = vec![0; expected_len * FOUR_BPP];
2162
2163 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2165 let red = rgb[0];
2166 let green = rgb[1];
2167 let blue = rgb[2];
2168
2169 px[pixel_index * FOUR_BPP] = blue;
2170 px[(pixel_index * FOUR_BPP) + 1] = green;
2171 px[(pixel_index * FOUR_BPP) + 2] = red;
2172 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2173 }
2174
2175 Some((px.into(), true))
2176 }
2177
2178 fn load_rgba8(
2179 pixels: RawImageData,
2180 expected_len: usize,
2181 premultiplied_alpha: bool,
2182 ) -> Option<(U8Vec, bool)> {
2183 let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
2184
2185 if pixels.len() != expected_len * FOUR_CHANNELS {
2186 return None;
2187 }
2188
2189 let mut is_opaque = true;
2190
2191 if premultiplied_alpha {
2194 for rgba in pixels.chunks_exact_mut(4) {
2195 let (r, gba) = rgba.split_first_mut()?;
2196 core::mem::swap(r, gba.get_mut(1)?);
2197 let a = rgba.get_mut(3)?;
2198 if *a != 255 {
2199 is_opaque = false;
2200 }
2201 }
2202 } else {
2203 for rgba in pixels.chunks_exact_mut(4) {
2204 let (r, gba) = rgba.split_first_mut()?;
2206 core::mem::swap(r, gba.get_mut(1)?);
2207 let a = rgba.get_mut(3)?;
2208 if *a != 255 {
2209 is_opaque = false;
2210 }
2211 premultiply_alpha(rgba); }
2213 }
2214
2215 Some((pixels.into(), is_opaque))
2216 }
2217
2218 fn load_r16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2219 let pixels = pixels.get_u16_vec()?;
2220
2221 if pixels.len() != expected_len {
2222 return None;
2223 }
2224
2225 let mut px = vec![0; expected_len * FOUR_BPP];
2226
2227 for (pixel_index, grey_u16) in pixels.as_ref().iter().enumerate() {
2229 let grey_u8 = normalize_u16(*grey_u16);
2230 px[pixel_index * FOUR_BPP] = grey_u8;
2231 px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
2232 px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
2233 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2234 }
2235
2236 Some((px.into(), true))
2237 }
2238
2239 fn load_rg16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2240 let pixels = pixels.get_u16_vec()?;
2241
2242 if pixels.len() != expected_len * TWO_CHANNELS {
2243 return None;
2244 }
2245
2246 let mut is_opaque = true;
2247 let mut px = vec![0; expected_len * FOUR_BPP];
2248
2249 for (pixel_index, greyalpha) in pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate() {
2251 let grey_u8 = normalize_u16(greyalpha[0]);
2252 let alpha_u8 = normalize_u16(greyalpha[1]);
2253
2254 if alpha_u8 != 255 {
2255 is_opaque = false;
2256 }
2257
2258 px[pixel_index * FOUR_BPP] = grey_u8;
2259 px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
2260 px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
2261 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2262 }
2263
2264 Some((px.into(), is_opaque))
2265 }
2266
2267 fn load_rgb16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2268 let pixels = pixels.get_u16_vec()?;
2269
2270 if pixels.len() != expected_len * THREE_CHANNELS {
2271 return None;
2272 }
2273
2274 let mut px = vec![0; expected_len * FOUR_BPP];
2275
2276 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2278 let red_u8 = normalize_u16(rgb[0]);
2279 let green_u8 = normalize_u16(rgb[1]);
2280 let blue_u8 = normalize_u16(rgb[2]);
2281
2282 px[pixel_index * FOUR_BPP] = blue_u8;
2283 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2284 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2285 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2286 }
2287
2288 Some((px.into(), true))
2289 }
2290
2291 fn load_rgba16(
2292 pixels: RawImageData,
2293 expected_len: usize,
2294 premultiplied_alpha: bool,
2295 ) -> Option<(U8Vec, bool)> {
2296 let pixels = pixels.get_u16_vec()?;
2297
2298 if pixels.len() != expected_len * FOUR_CHANNELS {
2299 return None;
2300 }
2301
2302 let mut is_opaque = true;
2303 let mut px = vec![0; expected_len * FOUR_BPP];
2304
2305 if premultiplied_alpha {
2307 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2308 let red_u8 = normalize_u16(rgba[0]);
2309 let green_u8 = normalize_u16(rgba[1]);
2310 let blue_u8 = normalize_u16(rgba[2]);
2311 let alpha_u8 = normalize_u16(rgba[3]);
2312
2313 if alpha_u8 != 255 {
2314 is_opaque = false;
2315 }
2316
2317 px[pixel_index * FOUR_BPP] = blue_u8;
2318 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2319 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2320 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2321 }
2322 } else {
2323 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2324 let red_u8 = normalize_u16(rgba[0]);
2325 let green_u8 = normalize_u16(rgba[1]);
2326 let blue_u8 = normalize_u16(rgba[2]);
2327 let alpha_u8 = normalize_u16(rgba[3]);
2328
2329 if alpha_u8 != 255 {
2330 is_opaque = false;
2331 }
2332
2333 px[pixel_index * FOUR_BPP] = blue_u8;
2334 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2335 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2336 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2337 premultiply_alpha(
2338 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2339 );
2340 }
2341 }
2342
2343 Some((px.into(), is_opaque))
2344 }
2345
2346 fn load_bgr8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2347 let pixels = pixels.get_u8_vec()?;
2348
2349 if pixels.len() != expected_len * THREE_CHANNELS {
2350 return None;
2351 }
2352
2353 let mut px = vec![0; expected_len * FOUR_BPP];
2354
2355 for (pixel_index, bgr) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2357 let blue = bgr[0];
2358 let green = bgr[1];
2359 let red = bgr[2];
2360
2361 px[pixel_index * FOUR_BPP] = blue;
2362 px[(pixel_index * FOUR_BPP) + 1] = green;
2363 px[(pixel_index * FOUR_BPP) + 2] = red;
2364 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2365 }
2366
2367 Some((px.into(), true))
2368 }
2369
2370 fn load_bgra8(
2371 pixels: RawImageData,
2372 expected_len: usize,
2373 premultiplied_alpha: bool,
2374 ) -> Option<(U8Vec, bool)> {
2375 let mut is_opaque = true;
2376
2377 let bytes: U8Vec = if premultiplied_alpha {
2378 let pixels = pixels.get_u8_vec()?;
2380
2381 if pixels.len() != expected_len * FOUR_BPP {
2382 return None;
2383 }
2384
2385 is_opaque = pixels
2386 .as_ref()
2387 .chunks_exact(FOUR_CHANNELS)
2388 .all(|bgra| bgra[3] == 255);
2389
2390 pixels
2391 } else {
2392 let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
2393
2394 if pixels.len() != expected_len * FOUR_BPP {
2395 return None;
2396 }
2397
2398 for bgra in pixels.chunks_exact_mut(FOUR_CHANNELS) {
2399 if bgra[3] != 255 {
2400 is_opaque = false;
2401 }
2402 premultiply_alpha(bgra);
2403 }
2404 pixels.into()
2405 };
2406
2407 Some((bytes, is_opaque))
2408 }
2409
2410 #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] #[allow(clippy::needless_pass_by_value)] fn load_rgbf32(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2414 let pixels = pixels.get_f32_vec_ref()?;
2415
2416 if pixels.len() != expected_len * THREE_CHANNELS {
2417 return None;
2418 }
2419
2420 let mut px = vec![0; expected_len * FOUR_BPP];
2421
2422 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2424 let red_u8 = (rgb[0] * 255.0) as u8;
2425 let green_u8 = (rgb[1] * 255.0) as u8;
2426 let blue_u8 = (rgb[2] * 255.0) as u8;
2427
2428 px[pixel_index * FOUR_BPP] = blue_u8;
2429 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2430 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2431 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2432 }
2433
2434 Some((px.into(), true))
2435 }
2436
2437 #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] #[allow(clippy::needless_pass_by_value)] fn load_rgbaf32(
2441 pixels: RawImageData,
2442 expected_len: usize,
2443 premultiplied_alpha: bool,
2444 ) -> Option<(U8Vec, bool)> {
2445 let pixels = pixels.get_f32_vec_ref()?;
2446
2447 if pixels.len() != expected_len * FOUR_CHANNELS {
2448 return None;
2449 }
2450
2451 let mut is_opaque = true;
2452 let mut px = vec![0; expected_len * FOUR_BPP];
2453
2454 if premultiplied_alpha {
2456 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2457 let red_u8 = (rgba[0] * 255.0) as u8;
2458 let green_u8 = (rgba[1] * 255.0) as u8;
2459 let blue_u8 = (rgba[2] * 255.0) as u8;
2460 let alpha_u8 = (rgba[3] * 255.0) as u8;
2461
2462 if alpha_u8 != 255 {
2463 is_opaque = false;
2464 }
2465
2466 px[pixel_index * FOUR_BPP] = blue_u8;
2467 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2468 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2469 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2470 }
2471 } else {
2472 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2473 let red_u8 = (rgba[0] * 255.0) as u8;
2474 let green_u8 = (rgba[1] * 255.0) as u8;
2475 let blue_u8 = (rgba[2] * 255.0) as u8;
2476 let alpha_u8 = (rgba[3] * 255.0) as u8;
2477
2478 if alpha_u8 != 255 {
2479 is_opaque = false;
2480 }
2481
2482 px[pixel_index * FOUR_BPP] = blue_u8;
2483 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2484 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2485 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2486 premultiply_alpha(
2487 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2488 );
2489 }
2490 }
2491
2492 Some((px.into(), is_opaque))
2493 }
2494}
2495
2496impl_option!(
2497 RawImage,
2498 OptionRawImage,
2499 copy = false,
2500 [Debug, Clone, PartialEq, PartialOrd]
2501);
2502
2503#[must_use] pub fn font_size_to_au(font_size: StyleFontSize) -> Au {
2504 Au::from_px(font_size.inner.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE))
2505}
2506
2507pub type FontInstanceFlags = u32;
2508
2509pub const FONT_INSTANCE_FLAG_SYNTHETIC_BOLD: u32 = 1 << 1;
2511pub const FONT_INSTANCE_FLAG_EMBEDDED_BITMAPS: u32 = 1 << 2;
2512pub const FONT_INSTANCE_FLAG_SUBPIXEL_BGR: u32 = 1 << 3;
2513pub const FONT_INSTANCE_FLAG_TRANSPOSE: u32 = 1 << 4;
2514pub const FONT_INSTANCE_FLAG_FLIP_X: u32 = 1 << 5;
2515pub const FONT_INSTANCE_FLAG_FLIP_Y: u32 = 1 << 6;
2516pub const FONT_INSTANCE_FLAG_SUBPIXEL_POSITION: u32 = 1 << 7;
2517
2518pub const FONT_INSTANCE_FLAG_FORCE_GDI: u32 = 1 << 16;
2520
2521pub const FONT_INSTANCE_FLAG_FONT_SMOOTHING: u32 = 1 << 16;
2523
2524pub const FONT_INSTANCE_FLAG_FORCE_AUTOHINT: u32 = 1 << 16;
2526pub const FONT_INSTANCE_FLAG_NO_AUTOHINT: u32 = 1 << 17;
2527pub const FONT_INSTANCE_FLAG_VERTICAL_LAYOUT: u32 = 1 << 18;
2528pub const FONT_INSTANCE_FLAG_LCD_VERTICAL: u32 = 1 << 19;
2529
2530#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2531pub struct GlyphOptions {
2532 pub render_mode: FontRenderMode,
2533 pub flags: FontInstanceFlags,
2534}
2535
2536#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2537pub enum FontRenderMode {
2538 Mono,
2539 Alpha,
2540 Subpixel,
2541}
2542
2543#[cfg(target_arch = "wasm32")]
2544#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2545pub struct FontInstancePlatformOptions {
2546 }
2548
2549#[cfg(target_os = "windows")]
2550#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2551pub struct FontInstancePlatformOptions {
2552 pub gamma: u16,
2553 pub contrast: u8,
2554 pub cleartype_level: u8,
2555}
2556
2557#[cfg(target_os = "macos")]
2558#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2559pub struct FontInstancePlatformOptions {
2560 pub unused: u32,
2561}
2562
2563#[cfg(target_os = "linux")]
2564#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2565pub struct FontInstancePlatformOptions {
2566 pub lcd_filter: FontLCDFilter,
2567 pub hinting: FontHinting,
2568}
2569
2570#[cfg(any(target_os = "android", target_os = "ios"))]
2574#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2575pub struct FontInstancePlatformOptions {
2576 pub unused: u32,
2577}
2578
2579#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2580pub enum FontHinting {
2581 None,
2582 Mono,
2583 Light,
2584 Normal,
2585 LCD,
2586}
2587
2588#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2589#[derive(Default)]
2590pub enum FontLCDFilter {
2591 None,
2592 #[default]
2593 Default,
2594 Light,
2595 Legacy,
2596}
2597
2598
2599#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2600pub struct FontInstanceOptions {
2601 pub render_mode: FontRenderMode,
2602 pub flags: FontInstanceFlags,
2603 pub bg_color: ColorU,
2604 pub synthetic_italics: SyntheticItalics,
2608}
2609
2610impl Default for FontInstanceOptions {
2611 fn default() -> Self {
2612 Self {
2613 render_mode: FontRenderMode::Subpixel,
2614 flags: 0,
2615 bg_color: ColorU::TRANSPARENT,
2616 synthetic_italics: SyntheticItalics::default(),
2617 }
2618 }
2619}
2620
2621#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2622#[derive(Default)]
2623pub struct SyntheticItalics {
2624 pub angle: i16,
2625}
2626
2627
2628#[derive(Debug)]
2634#[repr(C)]
2635pub struct SharedRawImageData {
2636 pub data: *const U8Vec,
2638 pub copies: *const AtomicUsize,
2640 pub run_destructor: bool,
2642}
2643
2644impl SharedRawImageData {
2645 #[must_use] pub fn new(data: U8Vec) -> Self {
2647 Self {
2648 data: Box::into_raw(Box::new(data)),
2649 copies: Box::into_raw(Box::new(AtomicUsize::new(1))),
2650 run_destructor: true,
2651 }
2652 }
2653
2654 #[must_use] pub fn as_ref(&self) -> &[u8] {
2656 unsafe { (*self.data).as_ref() }
2659 }
2660
2661 #[must_use] pub fn get_bytes(&self) -> &[u8] {
2663 self.as_ref()
2664 }
2665
2666 #[must_use] pub fn as_ptr(&self) -> *const u8 {
2668 unsafe { (*self.data).as_ref().as_ptr() }
2670 }
2671
2672 #[must_use] pub const fn len(&self) -> usize {
2674 unsafe { (*self.data).len() }
2676 }
2677
2678 #[must_use] pub const fn is_empty(&self) -> bool {
2680 self.len() == 0
2681 }
2682
2683 #[must_use] pub fn into_inner(self) -> Option<U8Vec> {
2686 unsafe {
2690 if self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) == Some(1) {
2691 let data = Box::from_raw(self.data.cast_mut());
2692 drop(Box::from_raw(self.copies.cast_mut()));
2693 core::mem::forget(self); Some(*data)
2695 } else {
2696 None
2697 }
2698 }
2699 }
2700}
2701
2702unsafe impl Send for SharedRawImageData {}
2705unsafe impl Sync for SharedRawImageData {}
2706
2707impl Clone for SharedRawImageData {
2708 fn clone(&self) -> Self {
2709 unsafe {
2712 self.copies
2713 .as_ref()
2714 .map(|m| m.fetch_add(1, AtomicOrdering::SeqCst));
2715 }
2716 Self {
2717 data: self.data,
2718 copies: self.copies,
2719 run_destructor: true,
2720 }
2721 }
2722}
2723
2724impl Drop for SharedRawImageData {
2725 fn drop(&mut self) {
2726 self.run_destructor = false;
2727 unsafe {
2731 let copies = (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst);
2732 if copies == 1 {
2733 drop(Box::from_raw(self.data.cast_mut()));
2734 drop(Box::from_raw(self.copies.cast_mut()));
2735 }
2736 }
2737 }
2738}
2739
2740impl PartialEq for SharedRawImageData {
2741 fn eq(&self, rhs: &Self) -> bool {
2742 core::ptr::eq(self.data, rhs.data)
2743 }
2744}
2745
2746impl Eq for SharedRawImageData {}
2747
2748impl PartialOrd for SharedRawImageData {
2749 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
2750 Some(self.cmp(other))
2751 }
2752}
2753
2754impl Ord for SharedRawImageData {
2755 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2756 (self.data as usize).cmp(&(other.data as usize))
2757 }
2758}
2759
2760impl Hash for SharedRawImageData {
2761 fn hash<H>(&self, state: &mut H)
2762 where
2763 H: Hasher,
2764 {
2765 (self.data as usize).hash(state);
2766 }
2767}
2768
2769#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2772#[repr(C, u8)]
2773pub enum ImageData {
2774 Raw(SharedRawImageData),
2777 External(ExternalImageData),
2780}
2781
2782#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
2784#[repr(C, u8)]
2785pub enum ExternalImageType {
2786 TextureHandle(ImageBufferKind),
2788 Buffer,
2790}
2791
2792#[repr(C)]
2796#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
2797pub struct ExternalImageId {
2798 pub inner: u64,
2799}
2800
2801static LAST_EXTERNAL_IMAGE_ID: AtomicUsize = AtomicUsize::new(0);
2802
2803impl Default for ExternalImageId {
2804 fn default() -> Self {
2805 Self::new()
2806 }
2807}
2808
2809impl ExternalImageId {
2810 pub fn new() -> Self {
2812 Self {
2813 inner: LAST_EXTERNAL_IMAGE_ID.fetch_add(1, AtomicOrdering::SeqCst) as u64,
2814 }
2815 }
2816}
2817
2818#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2819#[repr(C, u8)]
2820pub enum GlyphOutlineOperation {
2821 MoveTo(OutlineMoveTo),
2822 LineTo(OutlineLineTo),
2823 QuadraticCurveTo(OutlineQuadTo),
2824 CubicCurveTo(OutlineCubicTo),
2825 ClosePath,
2826}
2827
2828impl_option!(
2829 GlyphOutlineOperation,
2830 OptionGlyphOutlineOperation,
2831 copy = false,
2832 [Debug, Clone, PartialEq, Eq, PartialOrd]
2833);
2834
2835#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2837#[repr(C)]
2838pub struct OutlineMoveTo {
2839 pub x: i16,
2840 pub y: i16,
2841}
2842
2843#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2845#[repr(C)]
2846pub struct OutlineLineTo {
2847 pub x: i16,
2848 pub y: i16,
2849}
2850
2851#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2853#[repr(C)]
2854pub struct OutlineQuadTo {
2855 pub ctrl_1_x: i16,
2856 pub ctrl_1_y: i16,
2857 pub end_x: i16,
2858 pub end_y: i16,
2859}
2860
2861#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2863#[repr(C)]
2864pub struct OutlineCubicTo {
2865 pub ctrl_1_x: i16,
2866 pub ctrl_1_y: i16,
2867 pub ctrl_2_x: i16,
2868 pub ctrl_2_y: i16,
2869 pub end_x: i16,
2870 pub end_y: i16,
2871}
2872
2873#[derive(Debug, Clone, PartialEq, PartialOrd)]
2874#[repr(C)]
2875pub struct GlyphOutline {
2876 pub operations: GlyphOutlineOperationVec,
2877}
2878
2879azul_css::impl_vec!(GlyphOutlineOperation, GlyphOutlineOperationVec, GlyphOutlineOperationVecDestructor, GlyphOutlineOperationVecDestructorType, GlyphOutlineOperationVecSlice, OptionGlyphOutlineOperation);
2880azul_css::impl_vec_clone!(
2881 GlyphOutlineOperation,
2882 GlyphOutlineOperationVec,
2883 GlyphOutlineOperationVecDestructor
2884);
2885azul_css::impl_vec_debug!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2886azul_css::impl_vec_partialord!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2887azul_css::impl_vec_partialeq!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2888
2889#[derive(Debug, Clone, Copy)]
2890#[repr(C)]
2891pub struct OwnedGlyphBoundingBox {
2892 pub max_x: i16,
2893 pub max_y: i16,
2894 pub min_x: i16,
2895 pub min_y: i16,
2896}
2897
2898#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
2900#[repr(C)]
2901pub enum ImageBufferKind {
2902 Texture2D = 0,
2904 TextureRect = 1,
2911 TextureExternal = 2,
2916}
2917
2918#[repr(C)]
2920#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
2921pub struct ExternalImageData {
2922 pub id: ExternalImageId,
2924 pub channel_index: u8,
2927 pub image_type: ExternalImageType,
2929}
2930
2931pub type TileSize = u16;
2932
2933#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
2934pub enum ImageDirtyRect {
2935 All,
2936 Partial(LayoutRect),
2937}
2938
2939#[derive(Debug, Clone, PartialEq, PartialOrd)]
2940pub enum ResourceUpdate {
2941 AddFont(AddFont),
2942 DeleteFont(FontKey),
2943 AddFontInstance(AddFontInstance),
2944 DeleteFontInstance(FontInstanceKey),
2945 AddImage(AddImage),
2946 UpdateImage(UpdateImage),
2947 DeleteImage(ImageKey),
2948}
2949
2950#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2951pub struct AddImage {
2952 pub key: ImageKey,
2953 pub descriptor: ImageDescriptor,
2954 pub data: ImageData,
2955 pub tiling: Option<TileSize>,
2956}
2957
2958#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
2959pub struct UpdateImage {
2960 pub key: ImageKey,
2961 pub descriptor: ImageDescriptor,
2962 pub data: ImageData,
2963 pub dirty_rect: ImageDirtyRect,
2964}
2965
2966#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2969pub struct AddFont {
2970 pub key: FontKey,
2971 pub font: FontRef,
2972}
2973
2974impl fmt::Debug for AddFont {
2975 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2976 write!(
2977 f,
2978 "AddFont {{ key: {:?}, font: {:?} }}",
2979 self.key, self.font
2980 )
2981 }
2982}
2983
2984#[derive(Debug, Clone, PartialEq, PartialOrd)]
2985pub struct AddFontInstance {
2986 pub key: FontInstanceKey,
2987 pub font_key: FontKey,
2988 pub glyph_size: (Au, DpiScaleFactor),
2989 pub options: Option<FontInstanceOptions>,
2990 pub platform_options: Option<FontInstancePlatformOptions>,
2991 pub variations: Vec<FontVariation>,
2992}
2993
2994#[repr(C)]
2995#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
2996pub struct FontVariation {
2997 pub tag: u32,
2998 pub value: f32,
2999}
3000
3001#[repr(C)]
3002#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
3003pub struct Epoch {
3004 inner: u32,
3005}
3006
3007impl fmt::Display for Epoch {
3008 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3009 write!(f, "{}", self.inner)
3010 }
3011}
3012
3013impl Default for Epoch {
3014 fn default() -> Self {
3015 Self::new()
3016 }
3017}
3018
3019impl Epoch {
3020 #[must_use] pub const fn new() -> Self {
3024 Self { inner: 0 }
3025 }
3026 #[must_use] pub const fn from(i: u32) -> Self {
3027 Self { inner: i }
3028 }
3029 #[must_use] pub const fn into_u32(&self) -> u32 {
3030 self.inner
3031 }
3032
3033 pub const fn increment(&mut self) {
3036 use core::u32;
3037 const MAX_ID: u32 = u32::MAX - 1;
3038 *self = match self.inner {
3039 MAX_ID => Self { inner: 0 },
3040 other => Self {
3041 inner: other.saturating_add(1),
3042 },
3043 };
3044 }
3045}
3046
3047#[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Eq, Ord)]
3049pub struct Au(pub i32);
3050
3051pub const AU_PER_PX: i32 = 60;
3052pub const MAX_AU: i32 = (1 << 30) - 1;
3053pub const MIN_AU: i32 = -(1 << 30) - 1;
3054
3055impl Au {
3056 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] #[must_use] pub fn from_px(px: f32) -> Self {
3058 let target_app_units = (px * AU_PER_PX as f32) as i32;
3059 Self(target_app_units.clamp(MIN_AU, MAX_AU))
3060 }
3061 #[allow(clippy::cast_precision_loss)] #[must_use] pub fn into_px(&self) -> f32 {
3063 self.0 as f32 / AU_PER_PX as f32
3064 }
3065}
3066
3067#[derive(Debug)]
3069pub enum AddFontMsg {
3070 Font(FontKey, StyleFontFamilyHash, FontRef),
3072 Instance(AddFontInstance, (Au, DpiScaleFactor)),
3073}
3074
3075impl AddFontMsg {
3076 #[must_use] pub fn into_resource_update(&self) -> ResourceUpdate {
3077 use self::AddFontMsg::{Font, Instance};
3078 match self {
3079 Font(font_key, _, font_ref) => ResourceUpdate::AddFont(AddFont {
3080 key: *font_key,
3081 font: font_ref.clone(),
3082 }),
3083 Instance(fi, _) => ResourceUpdate::AddFontInstance(fi.clone()),
3084 }
3085 }
3086}
3087
3088#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
3089pub enum DeleteFontMsg {
3090 Font(FontKey),
3091 Instance(FontInstanceKey, (Au, DpiScaleFactor)),
3092}
3093
3094impl DeleteFontMsg {
3095 #[must_use] pub const fn into_resource_update(&self) -> ResourceUpdate {
3096 use self::DeleteFontMsg::{Font, Instance};
3097 match self {
3098 Font(f) => ResourceUpdate::DeleteFont(*f),
3099 Instance(fi, _) => ResourceUpdate::DeleteFontInstance(*fi),
3100 }
3101 }
3102}
3103
3104#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
3105pub struct AddImageMsg(pub AddImage);
3106
3107impl AddImageMsg {
3108 #[must_use] pub fn into_resource_update(&self) -> ResourceUpdate {
3109 ResourceUpdate::AddImage(self.0.clone())
3110 }
3111}
3112
3113#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3114#[repr(C)]
3115pub struct LoadedFontSource {
3116 pub data: U8Vec,
3117 pub index: u32,
3118 pub load_outlines: bool,
3119}
3120
3121pub type LoadFontFn = fn(&StyleFontFamily, &FcFontCache) -> Option<LoadedFontSource>;
3123
3124pub type ParseFontFn = fn(LoadedFontSource) -> Option<FontRef>; pub type GlStoreImageFn = fn(DocumentId, Epoch, Texture, ExternalImageId);
3128
3129#[must_use] pub fn texture_external_image_id(dom_id: DomId, node_id: NodeId) -> ExternalImageId {
3136 let dom = dom_id.inner as u64;
3137 let node = node_id.index() as u64;
3138 debug_assert!(u32::try_from(dom).is_ok(), "DomId exceeds 32-bit range");
3139 debug_assert!(u32::try_from(node).is_ok(), "NodeId exceeds 32-bit range");
3140 ExternalImageId {
3141 inner: (dom << 32) | (node & 0xFFFF_FFFF),
3142 }
3143}
3144
3145#[must_use] pub const fn image_ref_hash_to_external_image_id(hash: ImageRefHash) -> ExternalImageId {
3149 ExternalImageId {
3150 inner: hash.inner,
3151 }
3152}
3153
3154#[allow(clippy::too_many_lines)] pub fn build_add_font_resource_updates(
3164 renderer_resources: &mut RendererResources,
3165 dpi: DpiScaleFactor,
3166 fc_cache: &FcFontCache,
3167 id_namespace: IdNamespace,
3168 fonts_in_dom: &OrderedMap<ImmediateFontId, FastBTreeSet<Au>>,
3169 font_source_load_fn: LoadFontFn,
3170 parse_font_fn: ParseFontFn,
3171) -> Vec<(StyleFontFamilyHash, AddFontMsg)> {
3172 let mut resource_updates = Vec::new();
3173 let mut font_instances_added_this_frame = FastBTreeSet::new();
3174
3175 'outer: for (im_font_id, font_sizes) in fonts_in_dom {
3176 macro_rules! insert_font_instances {
3177 ($font_family_hash:expr, $font_key:expr, $font_size:expr) => {{
3178 let font_instance_key_exists = renderer_resources
3179 .currently_registered_fonts
3180 .get(&$font_key)
3181 .and_then(|(_, font_instances)| font_instances.get(&($font_size, dpi)))
3182 .is_some()
3183 || font_instances_added_this_frame.contains(&($font_key, ($font_size, dpi)));
3184
3185 if !font_instance_key_exists {
3186 let font_instance_key = FontInstanceKey::unique(id_namespace);
3187
3188 #[cfg(target_os = "windows")]
3190 let platform_options = FontInstancePlatformOptions {
3191 gamma: 300,
3192 contrast: 100,
3193 cleartype_level: 100,
3194 };
3195
3196 #[cfg(target_os = "linux")]
3197 let platform_options = FontInstancePlatformOptions {
3198 lcd_filter: FontLCDFilter::Default,
3199 hinting: FontHinting::Normal,
3200 };
3201
3202 #[cfg(target_os = "macos")]
3203 let platform_options = FontInstancePlatformOptions::default();
3204
3205 #[cfg(target_arch = "wasm32")]
3206 let platform_options = FontInstancePlatformOptions::default();
3207
3208 #[cfg(any(target_os = "android", target_os = "ios"))]
3209 let platform_options = FontInstancePlatformOptions::default();
3210
3211 let options = FontInstanceOptions {
3212 render_mode: FontRenderMode::Subpixel,
3213 flags: FONT_INSTANCE_FLAG_NO_AUTOHINT,
3214 ..Default::default()
3215 };
3216
3217 font_instances_added_this_frame.insert(($font_key, ($font_size, dpi)));
3218 resource_updates.push((
3219 $font_family_hash,
3220 AddFontMsg::Instance(
3221 AddFontInstance {
3222 key: font_instance_key,
3223 font_key: $font_key,
3224 glyph_size: ($font_size, dpi),
3225 options: Some(options),
3226 platform_options: Some(platform_options),
3227 variations: alloc::vec::Vec::new(),
3228 },
3229 ($font_size, dpi),
3230 ),
3231 ));
3232 }
3233 }};
3234 }
3235
3236 match im_font_id {
3237 ImmediateFontId::Resolved((font_family_hash, font_id)) => {
3238 for font_size in font_sizes {
3241 insert_font_instances!(*font_family_hash, *font_id, *font_size);
3242 }
3243 }
3244 ImmediateFontId::Unresolved(style_font_families) => {
3245 let mut font_family_hash = None;
3255 let font_families_hash = StyleFontFamiliesHash::new(style_font_families.as_ref());
3256
3257 'inner: for family in style_font_families.as_ref() {
3259 let current_family_hash = StyleFontFamilyHash::new(family);
3260
3261 if let Some(font_id) = renderer_resources.font_id_map.get(¤t_family_hash)
3262 {
3263 for font_size in font_sizes {
3265 insert_font_instances!(current_family_hash, *font_id, *font_size);
3266 }
3267 continue 'outer;
3268 }
3269
3270 let font_ref = match family {
3271 StyleFontFamily::Ref(r) => r.clone(), other => {
3273 let Some(font_data) = (font_source_load_fn)(other, fc_cache) else {
3275 continue 'inner;
3276 };
3277
3278
3279
3280 match (parse_font_fn)(font_data) {
3281 Some(s) => s,
3282 None => continue 'inner,
3283 }
3284 }
3285 };
3286
3287 font_family_hash = Some((current_family_hash, font_ref));
3289 break 'inner;
3290 }
3291
3292 let (font_family_hash, font_ref) = match font_family_hash {
3293 None => continue 'outer, Some(s) => s,
3295 };
3296
3297 let font_key = FontKey::unique(id_namespace);
3299 let add_font_msg = AddFontMsg::Font(font_key, font_family_hash, font_ref);
3300
3301 renderer_resources
3302 .font_id_map
3303 .insert(font_family_hash, font_key);
3304 renderer_resources
3305 .font_families_map
3306 .insert(font_families_hash, font_family_hash);
3307 resource_updates.push((font_family_hash, add_font_msg));
3308
3309 for font_size in font_sizes {
3311 insert_font_instances!(font_family_hash, font_key, *font_size);
3312 }
3313 }
3314 }
3315 }
3316
3317 resource_updates
3318}
3319
3320#[allow(unused_variables)]
3336pub fn build_add_image_resource_updates(
3337 renderer_resources: &RendererResources,
3338 id_namespace: IdNamespace,
3339 epoch: Epoch,
3340 document_id: &DocumentId,
3341 images_in_dom: &FastBTreeSet<ImageRef>,
3342 insert_into_active_gl_textures: GlStoreImageFn,
3343) -> Vec<(ImageRefHash, AddImageMsg)> {
3344 images_in_dom
3345 .iter()
3346 .filter_map(|image_ref| {
3347 let image_ref_hash = image_ref_get_hash(image_ref);
3348
3349 if renderer_resources
3350 .currently_registered_images
3351 .contains_key(&image_ref_hash)
3352 {
3353 return None;
3354 }
3355
3356 match image_ref.get_data() {
3359 DecodedImage::Gl(texture) => {
3360 let descriptor = texture.get_descriptor();
3361 let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
3362 let external_image_id = image_ref_hash_to_external_image_id(image_ref_hash);
3366 (insert_into_active_gl_textures)(
3368 *document_id,
3369 epoch,
3370 texture.clone(),
3371 external_image_id,
3372 );
3373 Some((
3374 image_ref_hash,
3375 AddImageMsg(AddImage {
3376 key,
3377 data: ImageData::External(ExternalImageData {
3378 id: external_image_id,
3379 channel_index: 0,
3380 image_type: ExternalImageType::TextureHandle(
3381 ImageBufferKind::Texture2D,
3382 ),
3383 }),
3384 descriptor,
3385 tiling: None,
3386 }),
3387 ))
3388 }
3389 DecodedImage::Raw((descriptor, data)) => {
3390 let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
3391 Some((
3392 image_ref_hash,
3393 AddImageMsg(AddImage {
3394 key,
3395 data: data.clone(), descriptor: *descriptor, tiling: None,
3399 }),
3400 ))
3401 }
3402 DecodedImage::NullImage { .. } | DecodedImage::Callback(_) => None,
3405 }
3406 })
3407 .collect()
3408}
3409
3410#[allow(clippy::needless_pass_by_value)] pub fn add_resources(
3417 renderer_resources: &mut RendererResources,
3418 all_resource_updates: &mut Vec<ResourceUpdate>,
3419 add_font_resources: Vec<(StyleFontFamilyHash, AddFontMsg)>,
3420 add_image_resources: Vec<(ImageRefHash, AddImageMsg)>,
3421) {
3422 all_resource_updates.extend(
3423 add_font_resources
3424 .iter()
3425 .map(|(_, f)| f.into_resource_update()),
3426 );
3427 all_resource_updates.extend(
3428 add_image_resources
3429 .iter()
3430 .map(|(_, i)| i.into_resource_update()),
3431 );
3432
3433 for (image_ref_hash, add_image_msg) in &add_image_resources {
3434 renderer_resources.currently_registered_images.insert(
3435 *image_ref_hash,
3436 ResolvedImage {
3437 key: add_image_msg.0.key,
3438 descriptor: add_image_msg.0.descriptor,
3439 },
3440 );
3441 renderer_resources
3444 .image_key_map
3445 .insert(add_image_msg.0.key, *image_ref_hash);
3446 }
3447
3448 for (_, add_font_msg) in add_font_resources {
3449 use self::AddFontMsg::{Font, Instance};
3450 match add_font_msg {
3451 Font(fk, font_family_hash, font_ref) => {
3452 renderer_resources
3453 .currently_registered_fonts
3454 .entry(fk)
3455 .or_insert_with(|| (font_ref.clone(), OrderedMap::default()));
3456
3457 renderer_resources
3459 .font_hash_map
3460 .insert(font_ref.get_hash(), fk);
3461 }
3462 Instance(fi, size) => {
3463 if let Some((_, instances)) = renderer_resources
3464 .currently_registered_fonts
3465 .get_mut(&fi.font_key)
3466 {
3467 instances.insert(size, fi.key);
3468 }
3469 }
3470 }
3471 }
3472}
3473
3474#[cfg(test)]
3475#[allow(clippy::items_after_statements, clippy::redundant_clone, clippy::cast_possible_truncation, clippy::cast_sign_loss, trivial_casts, clippy::borrow_as_ptr, clippy::cast_ptr_alignment, clippy::unused_self, unused_qualifications, unreachable_pub, private_interfaces)] mod tests {
3477 use super::*;
3478
3479 #[test]
3480 fn normalize_u16_maps_full_range() {
3481 assert_eq!(normalize_u16(0), 0);
3483 assert_eq!(normalize_u16(u16::MAX), 255);
3484 let mid = normalize_u16(u16::MAX / 2);
3486 assert!((126..=128).contains(&mid), "midpoint normalized to {mid}");
3487 assert_eq!(normalize_u16(256), 0);
3490 assert_eq!(normalize_u16(257), 1);
3491 }
3492
3493 #[test]
3494 fn load_bgra8_rejects_wrong_length() {
3495 let short = RawImageData::U8(vec![0u8; 4 * 3].into()); assert!(RawImage::load_bgra8(short, 4, true).is_none());
3499
3500 let ok = RawImageData::U8(vec![255u8; 4 * 4].into()); assert!(RawImage::load_bgra8(ok, 4, true).is_some());
3503
3504 let short2 = RawImageData::U8(vec![0u8; 4 * 2].into());
3506 assert!(RawImage::load_bgra8(short2, 4, false).is_none());
3507 }
3508
3509 #[test]
3512 fn imageref_get_data_reads_backing_box() {
3513 let img = ImageRef::null_image(2, 3, RawImageFormat::RGBA8, vec![7, 8]);
3515 match img.get_data() {
3516 DecodedImage::NullImage { width, height, tag, .. } => {
3517 assert_eq!((*width, *height), (2, 3));
3518 assert_eq!(tag.as_slice(), &[7, 8]);
3519 }
3520 _ => panic!("expected NullImage"),
3521 }
3522 }
3523
3524 #[test]
3525 fn imageref_clone_shares_refcount_and_identity() {
3526 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3529 let c = img.clone();
3530 assert_eq!(img, c); assert!(c.into_inner().is_none());
3533 assert!(img.into_inner().is_some());
3535 }
3536
3537 #[test]
3538 fn imageref_deep_copy_has_distinct_identity() {
3539 let img = ImageRef::null_image(4, 4, RawImageFormat::RGBA8, vec![1]);
3542 let d = img.deep_copy();
3543 assert_ne!(img, d);
3544 drop(img);
3545 assert_eq!(d.get_size().width as usize, 4);
3547 }
3548
3549 #[test]
3550 fn imageref_last_drop_frees_once() {
3551 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3554 let c = img.clone();
3555 drop(img);
3556 drop(c);
3557 }
3558
3559 #[test]
3560 fn imageref_get_callback_none_for_non_callback_and_when_shared() {
3561 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3564 assert!(img.get_image_callback().is_none());
3565 let c = img.clone();
3566 assert!(img.get_image_callback().is_none()); drop(c);
3568 }
3569
3570 #[test]
3571 fn shared_raw_image_data_read_paths() {
3572 let s = SharedRawImageData::new(vec![10u8, 20, 30].into());
3574 assert_eq!(s.as_ref(), &[10, 20, 30]);
3575 assert_eq!(s.len(), 3);
3576 assert!(!s.is_empty());
3577 assert_eq!(unsafe { *s.as_ptr() }, 10);
3578 assert!(SharedRawImageData::new(Vec::<u8>::new().into()).is_empty());
3579 }
3580
3581 #[test]
3582 fn shared_raw_image_data_clone_shares_alloc() {
3583 let s = SharedRawImageData::new(vec![1u8, 2, 3, 4].into());
3586 let c = s.clone();
3587 assert_eq!(s, c); assert_eq!(s.as_ptr(), c.as_ptr());
3589 assert!(c.into_inner().is_none()); let inner = s.into_inner().expect("sole owner extraction");
3591 assert_eq!(inner.as_ref(), &[1, 2, 3, 4]);
3592 }
3593
3594 #[test]
3595 fn shared_raw_image_data_last_drop_frees_once() {
3596 let s = SharedRawImageData::new(vec![0u8; 8].into());
3598 let c = s.clone();
3599 drop(s);
3600 drop(c);
3601 }
3602}
3603
3604#[cfg(test)]
3605#[allow(
3606 clippy::float_cmp,
3607 clippy::items_after_statements,
3608 clippy::redundant_clone,
3609 clippy::cast_possible_truncation,
3610 clippy::cast_precision_loss,
3611 clippy::cast_sign_loss,
3612 clippy::cast_lossless,
3613 clippy::unreadable_literal,
3614 clippy::too_many_lines,
3615 clippy::many_single_char_names,
3616 clippy::similar_names,
3617 unused_qualifications,
3618 unreachable_pub,
3619 private_interfaces
3620)] mod autotest_generated {
3622 use alloc::string::String;
3623
3624 use super::*;
3625
3626 fn dummy_font_ref() -> FontRef {
3635 static DUMMY_FONT_DATA: u8 = 0;
3636 extern "C" fn dummy_destructor(_: *mut core::ffi::c_void) {}
3637 FontRef::new(
3638 core::ptr::addr_of!(DUMMY_FONT_DATA).cast::<core::ffi::c_void>(),
3639 dummy_destructor,
3640 )
3641 }
3642
3643 fn load_font_none(_: &StyleFontFamily, _: &FcFontCache) -> Option<LoadedFontSource> {
3645 None
3646 }
3647
3648 fn parse_font_none(_: LoadedFontSource) -> Option<FontRef> {
3650 None
3651 }
3652
3653 fn store_gl_texture_noop(_: DocumentId, _: Epoch, _: Texture, _: ExternalImageId) {}
3655
3656 fn test_document_id() -> DocumentId {
3657 DocumentId {
3658 namespace_id: IdNamespace(7),
3659 id: 0,
3660 }
3661 }
3662
3663 fn rgba8_image(w: usize, h: usize) -> RawImage {
3665 RawImage {
3666 pixels: RawImageData::U8(vec![0u8; w * h * 4].into()),
3667 width: w,
3668 height: h,
3669 premultiplied_alpha: true,
3670 data_format: RawImageFormat::RGBA8,
3671 tag: Vec::new().into(),
3672 }
3673 }
3674
3675 fn opaque_red() -> ColorU {
3676 ColorU {
3677 r: 255,
3678 g: 0,
3679 b: 0,
3680 a: 255,
3681 }
3682 }
3683
3684 #[test]
3689 fn match_route_valid_minimal_positive_control() {
3690 let m = match_route("/user/:id", "/user/42").expect("documented example must match");
3692 assert_eq!(m.pattern.as_str(), "/user/:id");
3693 assert_eq!(m.get_param("id").map(AzString::as_str), Some("42"));
3694
3695 let root = match_route("/", "/").expect("root must match root");
3696 assert!(root.params.as_ref().is_empty());
3697
3698 assert!(match_route("/about", "/settings").is_none());
3699 }
3700
3701 #[test]
3702 fn match_route_empty_input_does_not_panic() {
3703 let m = match_route("", "").expect("empty vs empty is a zero-segment match");
3706 assert!(m.params.as_ref().is_empty());
3707 assert!(match_route("", "/").is_some()); assert!(match_route("/", "").is_some());
3709 assert!(match_route("", "/a").is_none()); assert!(match_route("/a", "").is_none());
3711 }
3712
3713 #[test]
3714 fn match_route_whitespace_only_is_not_trimmed() {
3715 assert!(match_route(" ", " ").is_some());
3718 assert!(match_route(" ", "\t\n").is_none());
3719 assert!(match_route("/ ", "/").is_none()); assert!(match_route("/\t\n", "/\t\n").is_some());
3721 }
3722
3723 #[test]
3724 fn match_route_garbage_never_panics() {
3725 for pat in [
3726 "\0\0\0",
3727 "///////",
3728 "::::",
3729 ":",
3730 "%%%$#@!",
3731 "\u{feff}",
3732 "/a/../../etc/passwd",
3733 ] {
3734 for path in ["", "/", "\0", "/a/b/c", "%%%$#@!", "\u{feff}"] {
3735 let _ = match_route(pat, path);
3737 }
3738 }
3739 assert!(match_route("///////", "/").is_some());
3741 let m = match_route("/:", "/hello").expect("empty param name still matches");
3743 assert_eq!(m.get_param("").map(AzString::as_str), Some("hello"));
3744 }
3745
3746 #[test]
3747 fn match_route_leading_trailing_junk_is_rejected_or_ignored() {
3748 assert!(match_route("/user/:id/", "/user/42").is_some());
3751 assert!(match_route("/user/:id", "/user/42/").is_some());
3752 assert!(match_route(" /about ", "/about").is_none());
3754 assert!(match_route("/about", "/about;garbage").is_none());
3755 }
3756
3757 #[test]
3758 fn match_route_boundary_number_strings_are_opaque_segments() {
3759 for v in [
3761 "0",
3762 "-0",
3763 "9223372036854775807",
3764 "-9223372036854775808",
3765 "1e400",
3766 "NaN",
3767 "inf",
3768 "-inf",
3769 "0.0000000000000000001",
3770 ] {
3771 let path = String::from("/user/") + v;
3772 let m = match_route("/user/:id", &path).expect("any segment matches a :param");
3773 assert_eq!(m.get_param("id").map(AzString::as_str), Some(v));
3774 }
3775 }
3776
3777 #[test]
3778 fn match_route_unicode_multibyte_does_not_panic() {
3779 let m = match_route("/user/:id", "/user/\u{1F600}").expect("emoji segment matches");
3781 assert_eq!(m.get_param("id").map(AzString::as_str), Some("\u{1F600}"));
3782
3783 let m = match_route("/:é\u{0301}", "/e\u{0301}\u{202E}x").expect("unicode param name");
3785 assert_eq!(
3786 m.get_param("é\u{0301}").map(AzString::as_str),
3787 Some("e\u{0301}\u{202E}x")
3788 );
3789 assert!(match_route("/é", "/e\u{0301}").is_none());
3791 }
3792
3793 #[test]
3794 fn match_route_extremely_long_input_does_not_hang() {
3795 let huge = String::from("/") + &"a".repeat(1_000_000);
3797 assert!(match_route("/x", &huge).is_none());
3798 let m = match_route("/:id", &huge).expect("one long segment is still one segment");
3799 assert_eq!(m.get_param("id").map(|s| s.as_str().len()), Some(1_000_000));
3800 }
3801
3802 #[test]
3803 fn match_route_deeply_nested_input_does_not_stack_overflow() {
3804 let deep = "/a".repeat(10_000);
3806 let m = match_route(&deep, &deep).expect("identical deep paths match");
3807 assert!(m.params.as_ref().is_empty());
3808
3809 let all_params = "/:p".repeat(10_000);
3810 let m = match_route(&all_params, &deep).expect("10k params extract");
3811 assert_eq!(m.params.as_ref().len(), 10_000);
3812 assert_eq!(m.get_param("p").map(AzString::as_str), Some("a"));
3814
3815 let brackets = String::from("/") + &"[".repeat(10_000);
3816 assert!(match_route("/:x", &brackets).is_some());
3817 }
3818
3819 #[test]
3820 fn match_route_segment_count_mismatch_is_none() {
3821 assert!(match_route("/a/:b", "/a").is_none());
3822 assert!(match_route("/a", "/a/b").is_none());
3823 assert!(match_route("/:a/:b/:c", "/1/2").is_none());
3824 }
3825
3826 #[test]
3827 fn route_match_get_param_missing_keys_return_none() {
3828 let empty = RouteMatch {
3829 pattern: AzString::from_const_str("/"),
3830 params: StringPairVec::from_vec(Vec::new()),
3831 };
3832 assert!(empty.get_param("").is_none());
3834 assert!(empty.get_param(" ").is_none());
3835 assert!(empty.get_param("\t\n").is_none());
3836 assert!(empty.get_param("\u{1F600}").is_none());
3837 assert!(empty.get_param("\0").is_none());
3838 assert!(empty.get_param(&"k".repeat(100_000)).is_none());
3839
3840 let m = match_route("/u/:id", "/u/7").expect("valid");
3842 assert_eq!(m.get_param("id").map(AzString::as_str), Some("7"));
3843 assert!(m.get_param("ID").is_none()); assert!(m.get_param("i").is_none()); assert!(m.get_param(":id").is_none()); }
3847
3848 #[test]
3849 fn app_config_match_route_for_path_adversarial_inputs() {
3850 let mut config = AppConfig::create();
3851 let cb: crate::callbacks::LayoutCallbackType = autotest_layout;
3852 extern "C" fn autotest_layout(
3853 _: RefAny,
3854 _: crate::callbacks::LayoutCallbackInfo,
3855 ) -> crate::dom::Dom {
3856 crate::dom::Dom::create_body()
3857 }
3858 config.add_route(AzString::from_const_str("/"), cb);
3859 config.add_route(AzString::from_const_str("/user/:id"), cb);
3860
3861 let (route, m) = config
3863 .match_route_for_path("/user/42")
3864 .expect("registered route must match");
3865 assert_eq!(route.pattern.as_str(), "/user/:id");
3866 assert_eq!(m.get_param("id").map(AzString::as_str), Some("42"));
3867
3868 assert!(config.match_route_for_path("").is_some());
3870 assert!(config.match_route_for_path("/").is_some());
3871
3872 assert!(config.match_route_for_path("/nope/nope/nope").is_none());
3874 assert!(config.match_route_for_path("\0\0").is_none());
3875 assert!(config.match_route_for_path(" ").is_none());
3876 let long = String::from("/user/") + &"9".repeat(500_000);
3877 assert!(config.match_route_for_path(&long).is_some());
3878 let m = config
3879 .match_route_for_path("/user/\u{1F600}")
3880 .expect("unicode param");
3881 assert_eq!(m.1.get_param("id").map(AzString::as_str), Some("\u{1F600}"));
3882 }
3883
3884 #[test]
3885 fn app_config_add_route_replaces_same_pattern_and_orders_by_insertion() {
3886 extern "C" fn layout_a(_: RefAny, _: crate::callbacks::LayoutCallbackInfo) -> crate::dom::Dom {
3887 crate::dom::Dom::create_body()
3888 }
3889 let cb: crate::callbacks::LayoutCallbackType = layout_a;
3890
3891 let mut config = AppConfig::create();
3892 assert!(config.routes.as_ref().is_empty());
3893 config.add_route(AzString::from_const_str("/dup"), cb);
3894 config.add_route(AzString::from_const_str("/dup"), cb);
3895 assert_eq!(config.routes.as_ref().len(), 1, "same pattern must replace");
3896
3897 let mut config = AppConfig::create();
3899 config.add_route(AzString::from_const_str("/:anything"), cb);
3900 config.add_route(AzString::from_const_str("/about"), cb);
3901 let (route, _) = config.match_route_for_path("/about").expect("matches");
3902 assert_eq!(route.pattern.as_str(), "/:anything");
3903 }
3904
3905 #[test]
3910 fn dpi_scale_factor_new_handles_nan_and_infinities() {
3911 assert_eq!(DpiScaleFactor::new(0.0).inner.get(), 0.0);
3913 assert_eq!(DpiScaleFactor::new(1.0).inner.get(), 1.0);
3914 assert_eq!(DpiScaleFactor::new(f32::NAN).inner.get(), 0.0);
3915 assert!(DpiScaleFactor::new(f32::INFINITY).inner.get().is_finite());
3916 assert!(DpiScaleFactor::new(f32::NEG_INFINITY).inner.get().is_finite());
3917 assert!(DpiScaleFactor::new(f32::MAX).inner.get().is_finite());
3918 assert!(DpiScaleFactor::new(f32::MIN).inner.get().is_finite());
3919 assert_eq!(DpiScaleFactor::new(f32::MIN_POSITIVE).inner.get(), 0.0);
3921 assert_eq!(DpiScaleFactor::new(1.5), DpiScaleFactor::new(1.5));
3923 assert_ne!(DpiScaleFactor::new(1.5), DpiScaleFactor::new(2.0));
3924 }
3925
3926 #[test]
3927 fn named_font_new_keeps_fields_verbatim() {
3928 let f = NamedFont::new(
3929 AzString::from_const_str(""),
3930 U8Vec::from_vec(Vec::new()),
3931 );
3932 assert_eq!(f.name.as_str(), "");
3933 assert!(f.bytes.as_ref().is_empty());
3934
3935 let bytes = vec![0u8, 255, 128];
3936 let f = NamedFont::new(AzString::from(String::from("\u{1F600}")), bytes.clone().into());
3937 assert_eq!(f.name.as_str(), "\u{1F600}");
3938 assert_eq!(f.bytes.as_ref(), bytes.as_slice());
3939 }
3940
3941 #[test]
3942 fn loaded_font_new_keeps_fields_verbatim_at_limits() {
3943 let f = LoadedFont::new(0, AzString::from_const_str(""), 0, false);
3944 assert_eq!(f.font_hash, 0);
3945 assert_eq!(f.num_glyphs, 0);
3946 assert!(!f.has_bytes);
3947
3948 let f = LoadedFont::new(u64::MAX, AzString::from_const_str("x"), u32::MAX, true);
3949 assert_eq!(f.font_hash, u64::MAX);
3950 assert_eq!(f.num_glyphs, u32::MAX);
3951 assert!(f.has_bytes);
3952 }
3953
3954 #[test]
3955 fn brush_new_defaults_and_extreme_radius() {
3956 let b = Brush::new(opaque_red(), 4.0);
3957 assert_eq!(b.radius, 4.0);
3958 assert_eq!(b.hardness, 0.5);
3959 assert_eq!(b.flow, 1.0);
3960 assert_eq!(b.spacing, 0.25);
3961 assert_eq!(b.color, opaque_red());
3962
3963 assert!(Brush::new(opaque_red(), f32::NAN).radius.is_nan());
3965 assert_eq!(Brush::new(opaque_red(), -0.0).radius, -0.0);
3966 assert_eq!(Brush::new(opaque_red(), f32::INFINITY).radius, f32::INFINITY);
3967 }
3968
3969 #[test]
3970 fn image_cache_new_is_empty_and_default_is_neutral() {
3971 let cache = ImageCache::new();
3972 assert!(cache.image_id_map.is_empty());
3973 assert!(ImageCache::default().image_id_map.is_empty());
3974 }
3975
3976 #[test]
3977 fn gl_texture_cache_empty_is_neutral() {
3978 let cache = GlTextureCache::empty();
3979 assert!(cache.solved_textures.is_empty());
3980 assert!(cache.hashes.is_empty());
3981 let d = GlTextureCache::default();
3982 assert!(d.solved_textures.is_empty());
3983 assert!(d.hashes.is_empty());
3984 }
3985
3986 #[test]
3987 fn external_image_id_new_is_monotonic() {
3988 let a = ExternalImageId::new();
3989 let b = ExternalImageId::new();
3990 assert!(b.inner > a.inner, "the counter must strictly increase");
3991 assert!(ExternalImageId::default().inner > b.inner);
3992 }
3993
3994 #[test]
3995 fn shared_raw_image_data_new_invariants() {
3996 let empty = SharedRawImageData::new(U8Vec::from_vec(Vec::new()));
3997 assert_eq!(empty.len(), 0);
3998 assert!(empty.is_empty());
3999 assert!(empty.as_ref().is_empty());
4000 assert!(empty.get_bytes().is_empty());
4001 assert!(!empty.as_ptr().is_null());
4003
4004 let big = SharedRawImageData::new(vec![7u8; 100_000].into());
4005 assert_eq!(big.len(), 100_000);
4006 assert!(!big.is_empty());
4007 assert_eq!(big.as_ref().len(), big.len());
4008 assert_eq!(big.get_bytes(), big.as_ref());
4009 assert_eq!(big.into_inner().expect("sole owner").as_ref().len(), 100_000);
4011 }
4012
4013 #[test]
4014 fn app_config_create_registers_builtins_and_defaults() {
4015 let config = AppConfig::create();
4016 assert_eq!(config.log_level, AppLogLevel::Error);
4017 assert!(!config.enable_visual_panic_hook);
4018 assert!(config.enable_logging_on_panic);
4019 assert_eq!(config.termination_behavior, AppTerminationBehavior::EndProcess);
4020 assert!(config.routes.as_ref().is_empty());
4021 assert!(matches!(
4022 config.mock_css_environment,
4023 OptionCssMockEnvironment::None
4024 ));
4025 let libs = config.component_libraries.as_ref();
4027 assert_eq!(libs.len(), 1);
4028 assert_eq!(libs[0].name.as_str(), "builtin");
4029 assert!(!libs[0].components.as_ref().is_empty());
4030 }
4031
4032 #[test]
4033 fn app_config_add_component_library_replaces_same_name() {
4034 let register: crate::xml::RegisterComponentLibraryFnType =
4035 crate::xml::register_builtin_components;
4036 let mut config = AppConfig::create();
4037 let n_builtin = config.component_libraries.as_ref()[0].components.as_ref().len();
4038
4039 config.add_component_library(AzString::from_const_str("builtin"), register);
4041 assert_eq!(config.component_libraries.as_ref().len(), 1);
4042 assert_eq!(
4043 config.component_libraries.as_ref()[0].components.as_ref().len(),
4044 n_builtin
4045 );
4046
4047 config.add_component_library(AzString::from_const_str(""), register);
4049 config.add_component_library(AzString::from_const_str("\u{1F600}"), register);
4050 assert_eq!(config.component_libraries.as_ref().len(), 3);
4051 assert_eq!(config.component_libraries.as_ref()[2].name.as_str(), "\u{1F600}");
4052 }
4053
4054 #[test]
4055 fn app_config_with_mock_environment_sets_the_option() {
4056 let config = AppConfig::create().with_mock_environment(CssMockEnvironment::dark_theme());
4057 match config.mock_css_environment {
4058 OptionCssMockEnvironment::Some(env) => {
4059 assert!(matches!(
4060 env.theme,
4061 azul_css::dynamic_selector::OptionThemeCondition::Some(
4062 azul_css::dynamic_selector::ThemeCondition::Dark
4063 )
4064 ));
4065 }
4066 OptionCssMockEnvironment::None => panic!("mock env must be Some"),
4067 }
4068 let config = AppConfig::create()
4070 .with_mock_environment(CssMockEnvironment::linux())
4071 .with_mock_environment(CssMockEnvironment::windows());
4072 match config.mock_css_environment {
4073 OptionCssMockEnvironment::Some(env) => assert!(matches!(
4074 env.os,
4075 azul_css::dynamic_selector::OptionOsCondition::Some(
4076 azul_css::dynamic_selector::OsCondition::Windows
4077 )
4078 )),
4079 OptionCssMockEnvironment::None => panic!("mock env must be Some"),
4080 }
4081 }
4082
4083 #[test]
4088 fn css_mock_environment_presets_only_set_their_own_field() {
4089 use azul_css::dynamic_selector::{
4090 OptionOsCondition, OptionThemeCondition, OsCondition, ThemeCondition,
4091 };
4092
4093 for (mock, os) in [
4094 (CssMockEnvironment::linux(), OsCondition::Linux),
4095 (CssMockEnvironment::windows(), OsCondition::Windows),
4096 (CssMockEnvironment::macos(), OsCondition::MacOS),
4097 ] {
4098 assert!(matches!(mock.os, OptionOsCondition::Some(o) if o == os));
4099 assert!(matches!(mock.theme, OptionThemeCondition::None));
4101 assert!(matches!(mock.viewport_width, azul_css::OptionF32::None));
4102 }
4103
4104 assert!(matches!(
4105 CssMockEnvironment::dark_theme().theme,
4106 OptionThemeCondition::Some(ThemeCondition::Dark)
4107 ));
4108 assert!(matches!(
4109 CssMockEnvironment::light_theme().theme,
4110 OptionThemeCondition::Some(ThemeCondition::Light)
4111 ));
4112 assert!(matches!(
4113 CssMockEnvironment::dark_theme().os,
4114 OptionOsCondition::None
4115 ));
4116 }
4117
4118 #[test]
4119 fn css_mock_environment_apply_to_overrides_only_set_fields() {
4120 use azul_css::dynamic_selector::{
4121 BoolCondition, DynamicSelectorContext, OptionOsCondition, OptionThemeCondition,
4122 OsCondition, ThemeCondition,
4123 };
4124
4125 let mut ctx = DynamicSelectorContext::default();
4127 let before_os = ctx.os;
4128 let before_lang = ctx.language.clone();
4129 let before_w = ctx.viewport_width;
4130 CssMockEnvironment::default().apply_to(&mut ctx);
4131 assert_eq!(ctx.os, before_os);
4132 assert_eq!(ctx.language.as_str(), before_lang.as_str());
4133 assert_eq!(ctx.viewport_width, before_w);
4134
4135 let mock = CssMockEnvironment {
4138 os: OptionOsCondition::Some(OsCondition::Windows),
4139 theme: OptionThemeCondition::Some(ThemeCondition::Dark),
4140 language: azul_css::OptionString::Some(AzString::from_const_str("de-DE")),
4141 viewport_width: azul_css::OptionF32::Some(f32::NAN),
4142 viewport_height: azul_css::OptionF32::Some(f32::INFINITY),
4143 prefers_reduced_motion: azul_css::OptionBool::Some(true),
4144 prefers_high_contrast: azul_css::OptionBool::Some(false),
4145 ..Default::default()
4146 };
4147 let mut ctx = DynamicSelectorContext::default();
4148 mock.apply_to(&mut ctx);
4149 assert_eq!(ctx.os, OsCondition::Windows);
4150 assert_eq!(ctx.theme, ThemeCondition::Dark);
4151 assert_eq!(ctx.language.as_str(), "de-DE");
4152 assert!(ctx.viewport_width.is_nan());
4153 assert_eq!(ctx.viewport_height, f32::INFINITY);
4154 assert_eq!(ctx.prefers_reduced_motion, BoolCondition::True);
4155 assert_eq!(ctx.prefers_high_contrast, BoolCondition::False);
4156
4157 let mut ctx2 = ctx.clone();
4159 mock.apply_to(&mut ctx2);
4160 assert_eq!(ctx2.os, ctx.os);
4161 assert_eq!(ctx2.theme, ctx.theme);
4162 }
4163
4164 #[test]
4169 fn brush_dab_coverage_boundaries_and_monotonicity() {
4170 assert_eq!(brush_dab_coverage(0.0, 0.5), 1.0);
4172 assert_eq!(brush_dab_coverage(1.0, 0.5), 0.0);
4173 assert_eq!(brush_dab_coverage(-5.0, 0.5), 1.0);
4175 assert_eq!(brush_dab_coverage(2.0, 0.5), 0.0);
4176 assert_eq!(brush_dab_coverage(f32::INFINITY, 0.5), 0.0);
4177 assert_eq!(brush_dab_coverage(f32::NEG_INFINITY, 0.5), 1.0);
4178
4179 let mut prev = f32::INFINITY;
4181 for i in 0..=100 {
4182 let t = i as f32 / 100.0;
4183 let c = brush_dab_coverage(t, 0.5);
4184 assert!((0.0..=1.0).contains(&c), "coverage {c} out of range at t={t}");
4185 assert!(c <= prev + 1.0e-6, "not monotonic at t={t}");
4186 prev = c;
4187 }
4188 }
4189
4190 #[test]
4191 fn brush_dab_coverage_hardness_limits_never_divide_by_zero() {
4192 assert_eq!(brush_dab_coverage(0.5, 1.0), 1.0);
4195 assert!(brush_dab_coverage(1.0, 1.0).is_finite());
4196 assert_eq!(brush_dab_coverage(1.0, 1.0), 1.0); assert_eq!(brush_dab_coverage(2.0, 1.0), 0.0);
4198
4199 assert_eq!(brush_dab_coverage(0.5, -10.0), brush_dab_coverage(0.5, 0.0));
4201 assert_eq!(brush_dab_coverage(0.5, 10.0), brush_dab_coverage(0.5, 1.0));
4202 assert_eq!(
4203 brush_dab_coverage(0.5, f32::NEG_INFINITY),
4204 brush_dab_coverage(0.5, 0.0)
4205 );
4206 assert!(brush_dab_coverage(0.5, f32::INFINITY).is_finite());
4207 }
4208
4209 #[test]
4210 fn brush_dab_coverage_nan_propagates_without_panicking() {
4211 assert!(brush_dab_coverage(f32::NAN, 0.5).is_nan());
4214 assert!(brush_dab_coverage(0.5, f32::NAN).is_nan());
4215 assert!(brush_dab_coverage(f32::NAN, f32::NAN).is_nan());
4216 }
4217
4218 #[test]
4219 fn normalize_u16_is_monotonic_and_saturating() {
4220 assert_eq!(normalize_u16(u16::MIN), 0);
4221 assert_eq!(normalize_u16(u16::MAX), u8::MAX);
4222 let mut prev = 0u8;
4223 for i in (0..=u16::MAX).step_by(97) {
4224 let v = normalize_u16(i);
4225 assert!(v >= prev, "normalize_u16 must be monotonic ({i} -> {v})");
4226 prev = v;
4227 }
4228 }
4229
4230 #[test]
4231 fn premultiply_alpha_ignores_non_4_byte_slices() {
4232 for len in [0usize, 1, 2, 3, 5, 8] {
4234 let mut buf = vec![200u8; len];
4235 let before = buf.clone();
4236 premultiply_alpha(&mut buf);
4237 assert_eq!(buf, before, "len {len} must be left untouched");
4238 }
4239 }
4240
4241 #[test]
4242 fn premultiply_alpha_boundary_values_never_overflow() {
4243 let mut opaque = [255u8, 128, 0, 255];
4245 premultiply_alpha(&mut opaque);
4246 assert_eq!(opaque, [255, 128, 0, 255]);
4247
4248 let mut transparent = [255u8, 255, 255, 0];
4250 premultiply_alpha(&mut transparent);
4251 assert_eq!(transparent, [0, 0, 0, 0]);
4252
4253 let mut half = [255u8, 128, 0, 128];
4255 premultiply_alpha(&mut half);
4256 assert_eq!(half, [128, 64, 0, 128]);
4257
4258 let mut max = [255u8, 255, 255, 255];
4260 premultiply_alpha(&mut max);
4261 assert_eq!(max, [255, 255, 255, 255]);
4262 }
4263
4264 #[test]
4265 fn au_from_px_saturates_at_limits_and_nan() {
4266 assert_eq!(Au::from_px(0.0).0, 0);
4267 assert_eq!(Au::from_px(-0.0).0, 0);
4268 assert_eq!(Au::from_px(1.0).0, AU_PER_PX);
4269 assert_eq!(Au::from_px(-1.0).0, -AU_PER_PX);
4270 assert_eq!(Au::from_px(f32::NAN).0, 0);
4272 assert_eq!(Au::from_px(f32::INFINITY).0, MAX_AU);
4274 assert_eq!(Au::from_px(f32::NEG_INFINITY).0, MIN_AU);
4275 assert_eq!(Au::from_px(f32::MAX).0, MAX_AU);
4276 assert_eq!(Au::from_px(f32::MIN).0, MIN_AU);
4277 for px in [-1.0e9_f32, -1.0, 0.5, 16.0, 1.0e9] {
4279 let au = Au::from_px(px).0;
4280 assert!((MIN_AU..=MAX_AU).contains(&au), "{px} -> {au} escaped the clamp");
4281 }
4282 }
4283
4284 #[test]
4285 fn au_px_round_trip_is_stable() {
4286 for px in [0.0_f32, 0.5, 1.0, 12.0, 16.0, 72.5, -3.25, 1000.0] {
4288 let back = Au::from_px(px).into_px();
4289 assert!(
4290 (back - px).abs() <= 1.0 / AU_PER_PX as f32,
4291 "{px} round-tripped to {back}"
4292 );
4293 }
4294 assert_eq!(Au::from_px(16.0).into_px(), 16.0);
4296 assert!(Au(MAX_AU).into_px().is_finite());
4298 assert!(Au(MIN_AU).into_px().is_finite());
4299 assert!(Au(i32::MIN).into_px().is_finite());
4300 assert!(Au(i32::MAX).into_px().is_finite());
4301 }
4302
4303 #[test]
4304 fn font_size_to_au_zero_negative_and_typical() {
4305 use azul_css::props::basic::PixelValue;
4306 let au = |px: isize| {
4307 font_size_to_au(StyleFontSize {
4308 inner: PixelValue::const_px(px),
4309 })
4310 .0
4311 };
4312 assert_eq!(au(0), 0);
4313 assert_eq!(au(16), 16 * AU_PER_PX);
4314 assert_eq!(au(-10), -10 * AU_PER_PX);
4315 assert!((MIN_AU..=MAX_AU).contains(&au(1_000_000)));
4317 assert!((MIN_AU..=MAX_AU).contains(&au(-1_000_000)));
4318 }
4319
4320 #[test]
4325 fn epoch_new_from_and_into_u32() {
4326 assert_eq!(Epoch::new().into_u32(), 0);
4327 assert_eq!(Epoch::default().into_u32(), 0);
4328 assert_eq!(Epoch::from(0).into_u32(), 0);
4329 assert_eq!(Epoch::from(1).into_u32(), 1);
4330 assert_eq!(Epoch::from(u32::MAX).into_u32(), u32::MAX);
4331 assert_eq!(Epoch::from(u32::MAX - 1).into_u32(), u32::MAX - 1);
4332 }
4333
4334 #[test]
4335 fn epoch_increment_wraps_at_max_minus_one_and_never_reaches_max() {
4336 let mut e = Epoch::new();
4337 e.increment();
4338 assert_eq!(e.into_u32(), 1);
4339
4340 let mut e = Epoch::from(u32::MAX - 1);
4342 e.increment();
4343 assert_eq!(e.into_u32(), 0, "MAX-1 must wrap to 0, never to u32::MAX");
4344
4345 let mut e = Epoch::from(u32::MAX);
4348 e.increment();
4349 assert_eq!(e.into_u32(), u32::MAX);
4350
4351 let mut e = Epoch::from(u32::MAX - 3);
4353 for _ in 0..8 {
4354 e.increment();
4355 assert_ne!(e.into_u32(), u32::MAX);
4356 }
4357 }
4358
4359 #[test]
4360 fn epoch_display_is_non_empty_for_edge_values() {
4361 assert_eq!(alloc::format!("{}", Epoch::new()), "0");
4362 assert_eq!(alloc::format!("{}", Epoch::from(42)), "42");
4363 assert_eq!(
4364 alloc::format!("{}", Epoch::from(u32::MAX)),
4365 alloc::format!("{}", u32::MAX)
4366 );
4367 assert!(!alloc::format!("{:?}", Epoch::default()).is_empty());
4368 }
4369
4370 #[test]
4371 fn id_namespace_display_and_debug_are_well_formed() {
4372 assert_eq!(alloc::format!("{}", IdNamespace(0)), "IdNamespace(0)");
4373 assert_eq!(
4374 alloc::format!("{}", IdNamespace(u32::MAX)),
4375 alloc::format!("IdNamespace({})", u32::MAX)
4376 );
4377 assert_eq!(
4379 alloc::format!("{:?}", IdNamespace(7)),
4380 alloc::format!("{}", IdNamespace(7))
4381 );
4382 }
4383
4384 #[test]
4389 fn unique_keys_are_strictly_increasing_and_keep_their_namespace() {
4390 let ns = IdNamespace(u32::MAX);
4391
4392 let a = ImageKey::unique(ns);
4393 let b = ImageKey::unique(ns);
4394 assert_eq!(a.namespace, ns);
4395 assert!(b.key > a.key, "ImageKey counter must strictly increase");
4396 assert_eq!(ImageKey::DUMMY.key, 0);
4398 assert_ne!(a, ImageKey::DUMMY);
4399
4400 let a = FontKey::unique(ns);
4401 let b = FontKey::unique(ns);
4402 assert_eq!(a.namespace, ns);
4403 assert!(b.key > a.key);
4404
4405 let a = FontInstanceKey::unique(IdNamespace(0));
4406 let b = FontInstanceKey::unique(IdNamespace(0));
4407 assert_eq!(a.namespace, IdNamespace(0));
4408 assert!(b.key > a.key);
4409 }
4410
4411 #[test]
4412 fn image_ref_id_counter_is_monotonic_and_never_zero() {
4413 let a = next_image_ref_id();
4415 let b = next_image_ref_id();
4416 assert!(a > 0 && b > a);
4417 }
4418
4419 #[test]
4420 fn image_ref_hash_conversions_are_lossless_and_agree() {
4421 let img = ImageRef::null_image(1, 1, RawImageFormat::RGBA8, Vec::new());
4422 let hash = img.get_hash();
4423 assert_eq!(hash, image_ref_get_hash(&img));
4424
4425 let key = image_ref_hash_to_image_key(hash, IdNamespace(9));
4426 assert_eq!(key.namespace, IdNamespace(9));
4427 assert_eq!(key.key, hash.inner, "the u64 id must survive verbatim");
4428
4429 let ext = image_ref_hash_to_external_image_id(hash);
4430 assert_eq!(ext.inner, hash.inner);
4431
4432 for inner in [0u64, 1, u64::MAX, u64::MAX - 1] {
4434 let h = ImageRefHash { inner };
4435 assert_eq!(image_ref_hash_to_image_key(h, IdNamespace(0)).key, inner);
4436 assert_eq!(image_ref_hash_to_external_image_id(h).inner, inner);
4437 }
4438 }
4439
4440 #[test]
4441 fn texture_external_image_id_is_deterministic_and_collision_free() {
4442 let id = |d: usize, n: usize| texture_external_image_id(DomId { inner: d }, NodeId::new(n));
4443
4444 assert_eq!(id(3, 7), id(3, 7));
4446 assert_eq!(id(0, 0).inner, 0);
4447 assert_eq!(id(1, 2).inner, (1u64 << 32) | 2);
4449 assert_ne!(id(0, 1), id(1, 0));
4451 assert_eq!(id(0, u32::MAX as usize).inner, u64::from(u32::MAX));
4453 assert_eq!(
4454 id(u32::MAX as usize, 0).inner,
4455 u64::from(u32::MAX) << 32
4456 );
4457 }
4458
4459 #[test]
4464 fn raw_image_data_typed_getters_only_match_their_own_variant() {
4465 let u8v = RawImageData::U8(vec![1u8, 2].into());
4466 let u16v = RawImageData::U16(vec![1u16, 2].into());
4467 let f32v = RawImageData::F32(vec![1.0f32, 2.0].into());
4468
4469 assert_eq!(u8v.get_u8_vec_ref().map(|v| v.len()), Some(2));
4470 assert!(u8v.get_u16_vec_ref().is_none());
4471 assert!(u8v.get_f32_vec_ref().is_none());
4472
4473 assert!(u16v.get_u8_vec_ref().is_none());
4474 assert_eq!(u16v.get_u16_vec_ref().map(|v| v.len()), Some(2));
4475 assert!(u16v.get_f32_vec_ref().is_none());
4476
4477 assert!(f32v.get_u8_vec_ref().is_none());
4478 assert!(f32v.get_u16_vec_ref().is_none());
4479 assert_eq!(f32v.get_f32_vec_ref().map(|v| v.len()), Some(2));
4480
4481 let empty = RawImageData::U8(U8Vec::from_vec(Vec::new()));
4483 assert_eq!(empty.get_u8_vec_ref().map(|v| v.len()), Some(0));
4484
4485 assert!(RawImageData::U8(vec![9u8].into()).get_u8_vec().is_some());
4487 assert!(RawImageData::U16(vec![9u16].into()).get_u8_vec().is_none());
4488 assert!(RawImageData::U16(vec![9u16].into()).get_u16_vec().is_some());
4489 assert!(RawImageData::F32(vec![9.0f32].into()).get_u16_vec().is_none());
4490 }
4491
4492 #[test]
4497 fn load_fns_reject_wrong_payload_type() {
4498 let u16_1px = || RawImageData::U16(vec![0u16; 4].into());
4501 let f32_1px = || RawImageData::F32(vec![0.0f32; 4].into());
4502 let u8_1px = || RawImageData::U8(vec![0u8; 4].into());
4503
4504 assert!(RawImage::load_r8(u16_1px(), 4).is_none());
4505 assert!(RawImage::load_rg8(f32_1px(), 2, true).is_none());
4506 assert!(RawImage::load_rgb8(u16_1px(), 1).is_none());
4507 assert!(RawImage::load_rgba8(f32_1px(), 1, true).is_none());
4508 assert!(RawImage::load_r16(u8_1px(), 4).is_none());
4509 assert!(RawImage::load_rg16(f32_1px(), 2).is_none());
4510 assert!(RawImage::load_rgb16(u8_1px(), 1).is_none());
4511 assert!(RawImage::load_rgba16(u8_1px(), 1, true).is_none());
4512 assert!(RawImage::load_bgr8(u16_1px(), 1).is_none());
4513 assert!(RawImage::load_bgra8(u16_1px(), 1, true).is_none());
4514 assert!(RawImage::load_rgbf32(u8_1px(), 1).is_none());
4515 assert!(RawImage::load_rgbaf32(u16_1px(), 1, true).is_none());
4516 }
4517
4518 #[test]
4519 fn load_fns_reject_every_wrong_length() {
4520 assert!(RawImage::load_r8(RawImageData::U8(vec![0u8; 3].into()), 4).is_none());
4522 assert!(RawImage::load_r8(RawImageData::U8(vec![0u8; 5].into()), 4).is_none());
4523 assert!(RawImage::load_rg8(RawImageData::U8(vec![0u8; 3].into()), 2, true).is_none());
4524 assert!(RawImage::load_rg8(RawImageData::U8(vec![0u8; 5].into()), 2, true).is_none());
4525 assert!(RawImage::load_rgb8(RawImageData::U8(vec![0u8; 5].into()), 2).is_none());
4526 assert!(RawImage::load_rgb8(RawImageData::U8(vec![0u8; 7].into()), 2).is_none());
4527 assert!(RawImage::load_rgba8(RawImageData::U8(vec![0u8; 7].into()), 2, true).is_none());
4528 assert!(RawImage::load_rgba8(RawImageData::U8(vec![0u8; 9].into()), 2, false).is_none());
4529 assert!(RawImage::load_r16(RawImageData::U16(vec![0u16; 3].into()), 4).is_none());
4530 assert!(RawImage::load_rg16(RawImageData::U16(vec![0u16; 3].into()), 2).is_none());
4531 assert!(RawImage::load_rgb16(RawImageData::U16(vec![0u16; 5].into()), 2).is_none());
4532 assert!(RawImage::load_rgba16(RawImageData::U16(vec![0u16; 7].into()), 2, true).is_none());
4533 assert!(RawImage::load_bgr8(RawImageData::U8(vec![0u8; 5].into()), 2).is_none());
4534 assert!(RawImage::load_bgra8(RawImageData::U8(vec![0u8; 7].into()), 2, false).is_none());
4535 assert!(RawImage::load_rgbf32(RawImageData::F32(vec![0.0f32; 5].into()), 2).is_none());
4536 assert!(
4537 RawImage::load_rgbaf32(RawImageData::F32(vec![0.0f32; 7].into()), 2, true).is_none()
4538 );
4539 }
4540
4541 #[test]
4542 fn load_fns_accept_zero_pixels() {
4543 let empty_u8 = || RawImageData::U8(U8Vec::from_vec(Vec::new()));
4545 let empty_u16 = || RawImageData::U16(U16Vec::from_vec(Vec::new()));
4546 let empty_f32 = || RawImageData::F32(F32Vec::from_vec(Vec::new()));
4547
4548 assert_eq!(RawImage::load_r8(empty_u8(), 0).map(|(b, o)| (b.len(), o)), Some((0, false)));
4549 assert_eq!(RawImage::load_rg8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4550 assert_eq!(RawImage::load_rgb8(empty_u8(), 0).map(|(b, _)| b.len()), Some(0));
4551 assert_eq!(RawImage::load_rgba8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4552 assert_eq!(RawImage::load_r16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4553 assert_eq!(RawImage::load_rg16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4554 assert_eq!(RawImage::load_rgb16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4555 assert_eq!(RawImage::load_rgba16(empty_u16(), 0, true).map(|(b, _)| b.len()), Some(0));
4556 assert_eq!(RawImage::load_bgr8(empty_u8(), 0).map(|(b, _)| b.len()), Some(0));
4557 assert_eq!(RawImage::load_bgra8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4558 assert_eq!(RawImage::load_rgbf32(empty_f32(), 0).map(|(b, _)| b.len()), Some(0));
4559 assert_eq!(RawImage::load_rgbaf32(empty_f32(), 0, true).map(|(b, _)| b.len()), Some(0));
4560 }
4561
4562 #[test]
4563 fn load_r8_passes_data_through_and_is_never_opaque() {
4564 let (bytes, is_opaque) =
4566 RawImage::load_r8(RawImageData::U8(vec![0u8, 128, 255, 1].into()), 4)
4567 .expect("exact length");
4568 assert_eq!(bytes.as_ref(), &[0, 128, 255, 1]);
4569 assert!(!is_opaque, "R8 is documented as never opaque");
4570 }
4571
4572 #[test]
4573 fn load_rgb8_and_bgr8_swizzle_to_bgra_opaque() {
4574 let (bytes, is_opaque) =
4576 RawImage::load_rgb8(RawImageData::U8(vec![1u8, 2, 3].into()), 1).expect("1 px");
4577 assert_eq!(bytes.as_ref(), &[3, 2, 1, 255]);
4578 assert!(is_opaque);
4579
4580 let (bytes, is_opaque) =
4582 RawImage::load_bgr8(RawImageData::U8(vec![1u8, 2, 3].into()), 1).expect("1 px");
4583 assert_eq!(bytes.as_ref(), &[1, 2, 3, 255]);
4584 assert!(is_opaque);
4585 }
4586
4587 #[test]
4588 fn load_rgba8_swizzles_and_detects_transparency() {
4589 let (bytes, is_opaque) =
4591 RawImage::load_rgba8(RawImageData::U8(vec![10u8, 20, 30, 255].into()), 1, true)
4592 .expect("1 px");
4593 assert_eq!(bytes.as_ref(), &[30, 20, 10, 255]);
4594 assert!(is_opaque);
4595
4596 let (_, is_opaque) =
4598 RawImage::load_rgba8(RawImageData::U8(vec![0u8, 0, 0, 254].into()), 1, true)
4599 .expect("1 px");
4600 assert!(!is_opaque);
4601
4602 let (bytes, is_opaque) =
4604 RawImage::load_rgba8(RawImageData::U8(vec![10u8, 20, 30, 128].into()), 1, false)
4605 .expect("1 px");
4606 assert_eq!(bytes.as_ref(), &[15, 10, 5, 128]);
4607 assert!(!is_opaque);
4608
4609 let (bytes, _) =
4611 RawImage::load_rgba8(RawImageData::U8(vec![255u8, 255, 255, 0].into()), 1, false)
4612 .expect("1 px");
4613 assert_eq!(bytes.as_ref(), &[0, 0, 0, 0]);
4614 }
4615
4616 #[test]
4617 fn load_rg8_expands_grey_to_bgra() {
4618 let (bytes, is_opaque) =
4620 RawImage::load_rg8(RawImageData::U8(vec![100u8, 255].into()), 1, true).expect("1 px");
4621 assert_eq!(bytes.as_ref(), &[100, 100, 100, 255]);
4622 assert!(is_opaque);
4623
4624 let (bytes, is_opaque) =
4625 RawImage::load_rg8(RawImageData::U8(vec![100u8, 128].into()), 1, false).expect("1 px");
4626 assert_eq!(bytes.as_ref(), &[50, 50, 50, 128]);
4627 assert!(!is_opaque);
4628 }
4629
4630 #[test]
4631 fn load_16_bit_formats_normalize_to_8_bit() {
4632 let (bytes, is_opaque) =
4634 RawImage::load_r16(RawImageData::U16(vec![u16::MAX].into()), 1).expect("1 px");
4635 assert_eq!(bytes.as_ref(), &[255, 255, 255, 255]);
4636 assert!(is_opaque);
4637
4638 let (bytes, is_opaque) =
4639 RawImage::load_rg16(RawImageData::U16(vec![0u16, u16::MAX].into()), 1).expect("1 px");
4640 assert_eq!(bytes.as_ref(), &[0, 0, 0, 255]);
4641 assert!(is_opaque);
4642
4643 let (bytes, _) = RawImage::load_rgb16(
4645 RawImageData::U16(vec![u16::MAX, 0, 0].into()),
4646 1,
4647 )
4648 .expect("1 px");
4649 assert_eq!(bytes.as_ref(), &[0, 0, 255, 255]);
4650
4651 let (bytes, is_opaque) = RawImage::load_rgba16(
4653 RawImageData::U16(vec![u16::MAX, u16::MAX, u16::MAX, 0].into()),
4654 1,
4655 false,
4656 )
4657 .expect("1 px");
4658 assert_eq!(bytes.as_ref(), &[0, 0, 0, 0]);
4659 assert!(!is_opaque);
4660 }
4661
4662 #[test]
4663 fn load_f32_formats_saturate_on_out_of_range_nan_and_inf() {
4664 let (bytes, is_opaque) = RawImage::load_rgbf32(
4667 RawImageData::F32(vec![2.0f32, -1.0, f32::NAN].into()),
4668 1,
4669 )
4670 .expect("1 px");
4671 assert_eq!(bytes.as_ref(), &[0, 0, 255, 255], "b=NaN->0, g=-1->0, r=2.0->255");
4672 assert!(is_opaque);
4673
4674 let (bytes, is_opaque) = RawImage::load_rgbaf32(
4675 RawImageData::F32(vec![f32::INFINITY, f32::NEG_INFINITY, 0.5, 1.0].into()),
4676 1,
4677 true,
4678 )
4679 .expect("1 px");
4680 assert_eq!(bytes.as_ref(), &[127, 0, 255, 255]);
4681 assert!(is_opaque);
4682
4683 let (_, is_opaque) = RawImage::load_rgbaf32(
4685 RawImageData::F32(vec![1.0f32, 1.0, 1.0, f32::NAN].into()),
4686 1,
4687 true,
4688 )
4689 .expect("1 px");
4690 assert!(!is_opaque);
4691 }
4692
4693 #[test]
4698 fn raw_image_null_image_encodes_to_an_empty_bgra8_descriptor() {
4699 let null = RawImage::null_image();
4700 assert_eq!(null.width, 0);
4701 assert_eq!(null.height, 0);
4702 assert_eq!(null.data_format, RawImageFormat::BGRA8);
4703 assert!(null.premultiplied_alpha);
4704
4705 let (data, descriptor) = null
4706 .into_loaded_image_source()
4707 .expect("a 0x0 image is still a valid (empty) source");
4708 assert_eq!(descriptor.width, 0);
4709 assert_eq!(descriptor.height, 0);
4710 assert_eq!(descriptor.format, RawImageFormat::BGRA8);
4711 assert_eq!(descriptor.offset, 0);
4712 match data {
4713 ImageData::Raw(bytes) => assert!(bytes.is_empty()),
4714 ImageData::External(_) => panic!("a RawImage must never encode to External"),
4715 }
4716 }
4717
4718 #[test]
4719 fn raw_image_allocate_mask_zero_and_negative_sizes() {
4720 let mask = RawImage::allocate_mask(LayoutSize::zero());
4721 assert_eq!(mask.data_format, RawImageFormat::R8);
4722 assert_eq!(mask.width, 0);
4723 assert_eq!(mask.height, 0);
4724 assert_eq!(mask.pixels.get_u8_vec_ref().map(|v| v.len()), Some(0));
4725
4726 let mask = RawImage::allocate_mask(LayoutSize::new(4, 4));
4727 assert_eq!(mask.pixels.get_u8_vec_ref().map(|v| v.len()), Some(16));
4728 assert!(mask
4729 .pixels
4730 .get_u8_vec_ref()
4731 .expect("u8")
4732 .as_ref()
4733 .iter()
4734 .all(|b| *b == 0));
4735
4736 let mask = RawImage::allocate_mask(LayoutSize::new(-4, 4));
4741 assert_eq!(
4742 mask.pixels.get_u8_vec_ref().map(|v| v.len()),
4743 Some(0),
4744 "a negative extent must never allocate"
4745 );
4746 assert!(mask.width > 1_000_000, "negative width wraps via `as usize`");
4747 }
4748
4749 #[test]
4750 fn raw_image_mask_round_trips_as_r8() {
4751 let mask = RawImage::allocate_mask(LayoutSize::new(2, 2));
4754 let (data, descriptor) = mask.into_loaded_image_source().expect("consistent mask");
4755 assert_eq!(descriptor.format, RawImageFormat::R8);
4756 assert_eq!((descriptor.width, descriptor.height), (2, 2));
4757 assert!(!descriptor.flags.is_opaque, "R8 is never opaque");
4758 match data {
4759 ImageData::Raw(bytes) => assert_eq!(bytes.len(), 4),
4760 ImageData::External(_) => panic!("expected raw bytes"),
4761 }
4762 }
4763
4764 #[test]
4765 fn raw_image_rgba8_encode_decode_round_trip() {
4766 let raw = RawImage {
4769 pixels: RawImageData::U8(vec![10u8, 20, 30, 255].into()),
4770 width: 1,
4771 height: 1,
4772 premultiplied_alpha: true,
4773 data_format: RawImageFormat::RGBA8,
4774 tag: Vec::new().into(),
4775 };
4776 let img = ImageRef::new_rawimage(raw).expect("1x1 RGBA8 with 4 bytes is valid");
4777
4778 assert!(img.is_raw_image());
4779 assert!(!img.is_null_image());
4780 assert!(!img.is_gl_texture());
4781 assert!(!img.is_callback());
4782 assert_eq!(img.get_size(), LogicalSize::new(1.0, 1.0));
4783 assert_eq!(img.get_bytes(), Some(&[30u8, 20, 10, 255][..]));
4784 assert!(!img.get_bytes_ptr().is_null());
4785
4786 let decoded = img.get_rawimage().expect("raw image round-trips");
4787 assert_eq!(decoded.width, 1);
4788 assert_eq!(decoded.height, 1);
4789 assert_eq!(decoded.data_format, RawImageFormat::BGRA8);
4790 assert!(decoded.premultiplied_alpha);
4791 assert_eq!(
4792 decoded.pixels.get_u8_vec_ref().map(|v| v.as_ref().to_vec()),
4793 Some(vec![30, 20, 10, 255])
4794 );
4795 }
4796
4797 #[test]
4798 fn image_ref_new_rawimage_rejects_dimension_mismatch() {
4799 let too_small = RawImage {
4801 pixels: RawImageData::U8(vec![0u8; 4].into()),
4802 width: 2,
4803 height: 2,
4804 premultiplied_alpha: true,
4805 data_format: RawImageFormat::RGBA8,
4806 tag: Vec::new().into(),
4807 };
4808 assert!(ImageRef::new_rawimage(too_small).is_none());
4809
4810 let too_big = RawImage {
4812 pixels: RawImageData::U8(vec![0u8; 64].into()),
4813 width: 2,
4814 height: 2,
4815 premultiplied_alpha: true,
4816 data_format: RawImageFormat::RGBA8,
4817 tag: Vec::new().into(),
4818 };
4819 assert!(ImageRef::new_rawimage(too_big).is_none());
4820
4821 let wrong_type = RawImage {
4823 pixels: RawImageData::U16(vec![0u16; 16].into()),
4824 width: 2,
4825 height: 2,
4826 premultiplied_alpha: true,
4827 data_format: RawImageFormat::RGBA8,
4828 tag: Vec::new().into(),
4829 };
4830 assert!(ImageRef::new_rawimage(wrong_type).is_none());
4831 }
4832
4833 #[test]
4838 fn image_ref_null_image_predicates_and_accessors() {
4839 let img = ImageRef::null_image(0, 0, RawImageFormat::BGRA8, Vec::new());
4840 assert!(img.is_null_image());
4841 assert!(!img.is_raw_image());
4842 assert!(!img.is_gl_texture());
4843 assert!(!img.is_callback());
4844 assert_eq!(img.get_size(), LogicalSize::new(0.0, 0.0));
4845 assert!(img.get_bytes().is_none());
4846 assert!(img.get_rawimage().is_none());
4847 assert!(img.get_bytes_ptr().is_null());
4848 assert!(img.get_image_callback().is_none());
4849 assert!(matches!(img.get_data(), DecodedImage::NullImage { .. }));
4850 }
4851
4852 #[test]
4853 fn image_ref_null_image_at_usize_max_reports_a_finite_size() {
4854 let img = ImageRef::null_image(usize::MAX, usize::MAX, RawImageFormat::R8, Vec::new());
4856 let size = img.get_size();
4857 assert!(size.width.is_finite() && size.height.is_finite());
4858 assert!(size.width > 0.0 && size.height > 0.0);
4859 assert!(img.is_null_image());
4860
4861 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, vec![9u8; 10_000]);
4863 match img.get_data() {
4864 DecodedImage::NullImage { tag, .. } => assert_eq!(tag.len(), 10_000),
4865 _ => panic!("expected NullImage"),
4866 }
4867 }
4868
4869 #[test]
4870 fn image_ref_hash_identity_rules() {
4871 let a = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4872 let b = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4873 assert_ne!(a.get_hash(), b.get_hash());
4875 assert_ne!(a, b);
4876
4877 let a2 = a.clone();
4879 assert_eq!(a.get_hash(), a2.get_hash());
4880 assert_eq!(a, a2);
4881
4882 let deep = a.deep_copy();
4884 assert_ne!(a.get_hash(), deep.get_hash());
4885 assert!(deep.is_null_image());
4886 assert_eq!(deep.get_size(), a.get_size());
4887 }
4888
4889 #[test]
4890 fn image_ref_callback_accessors() {
4891 let mut img = ImageRef::callback(0usize, RefAny::new(123u32));
4894 assert!(img.is_callback());
4895 assert!(!img.is_null_image());
4896 assert!(!img.is_raw_image());
4897 assert_eq!(img.get_size(), LogicalSize::new(0.0, 0.0));
4899 assert!(img.get_bytes().is_none());
4900 assert!(img.get_bytes_ptr().is_null());
4901 assert!(img.get_rawimage().is_none());
4902
4903 assert!(img.get_image_callback().is_some());
4905 assert!(img.get_image_callback_mut().is_some());
4906
4907 let clone = img.clone();
4910 assert!(img.get_image_callback().is_none());
4911 assert!(img.get_image_callback_mut().is_none());
4912 drop(clone);
4913 assert!(img.get_image_callback().is_some());
4914 }
4915
4916 #[test]
4917 fn image_ref_deep_copy_of_a_callback_keeps_it_a_callback() {
4918 let img = ImageRef::callback(0usize, RefAny::new(1u8));
4919 let deep = img.deep_copy();
4920 assert!(deep.is_callback());
4921 assert_ne!(img.get_hash(), deep.get_hash());
4922 }
4923
4924 #[test]
4925 fn image_ref_into_inner_only_when_sole_owner() {
4926 let img = ImageRef::null_image(2, 2, RawImageFormat::RGBA8, vec![1, 2, 3]);
4927 let clone = img.clone();
4928 assert!(clone.into_inner().is_none(), "shared -> must refuse");
4929
4930 let inner = img.into_inner().expect("sole owner -> takes ownership");
4931 match inner {
4932 DecodedImage::NullImage {
4933 width,
4934 height,
4935 format,
4936 tag,
4937 } => {
4938 assert_eq!((width, height), (2, 2));
4939 assert_eq!(format, RawImageFormat::RGBA8);
4940 assert_eq!(tag, vec![1, 2, 3]);
4941 }
4942 _ => panic!("expected NullImage"),
4943 }
4944 }
4945
4946 #[test]
4951 fn image_cache_add_get_delete_round_trip() {
4952 let mut cache = ImageCache::new();
4953 let key = AzString::from_const_str("my_image");
4954 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4955 let hash = img.get_hash();
4956
4957 assert!(cache.get_css_image_id(&key).is_none());
4958 cache.add_css_image_id(key.clone(), img);
4959 assert_eq!(cache.get_css_image_id(&key).map(ImageRef::get_hash), Some(hash));
4960
4961 let img2 = ImageRef::null_image(2, 2, RawImageFormat::R8, Vec::new());
4963 let hash2 = img2.get_hash();
4964 cache.add_css_image_id(key.clone(), img2);
4965 assert_eq!(cache.image_id_map.len(), 1);
4966 assert_eq!(cache.get_css_image_id(&key).map(ImageRef::get_hash), Some(hash2));
4967
4968 cache.delete_css_image_id(&key);
4969 assert!(cache.get_css_image_id(&key).is_none());
4970 assert!(cache.image_id_map.is_empty());
4971 cache.delete_css_image_id(&key);
4973 cache.delete_css_image_id(&AzString::from_const_str("never-existed"));
4974 }
4975
4976 #[test]
4977 fn image_cache_handles_empty_and_unicode_keys() {
4978 let mut cache = ImageCache::new();
4979 let empty = AzString::from_const_str("");
4980 let unicode = AzString::from(String::from("\u{1F600}\u{0301}"));
4981 let long = AzString::from("k".repeat(100_000));
4982
4983 cache.add_css_image_id(
4984 empty.clone(),
4985 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4986 );
4987 cache.add_css_image_id(
4988 unicode.clone(),
4989 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4990 );
4991 cache.add_css_image_id(
4992 long.clone(),
4993 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4994 );
4995
4996 assert_eq!(cache.image_id_map.len(), 3);
4997 assert!(cache.get_css_image_id(&empty).is_some());
4998 assert!(cache.get_css_image_id(&unicode).is_some());
4999 assert!(cache.get_css_image_id(&long).is_some());
5000 assert!(cache
5002 .get_css_image_id(&AzString::from_const_str("\u{1F600}"))
5003 .is_none());
5004 }
5005
5006 #[test]
5011 fn renderer_resources_lookups_on_an_empty_registry_are_none() {
5012 let rr = RendererResources::default();
5013 let ns = IdNamespace(1);
5014 assert!(rr
5015 .get_renderable_font_data(&FontInstanceKey::unique(ns))
5016 .is_none());
5017 let families = StyleFontFamiliesHash::new(&[]);
5018 assert!(rr
5019 .get_font_instance_key(&families, Au(0), DpiScaleFactor::new(1.0))
5020 .is_none());
5021 assert!(rr
5022 .get_font_instance_key(&families, Au(MAX_AU), DpiScaleFactor::new(f32::NAN))
5023 .is_none());
5024 assert!(rr.get_image(&ImageRefHash { inner: 0 }).is_none());
5025 assert!(rr.get_font_key(&StyleFontFamilyHash::new(&StyleFontFamily::System(
5026 AzString::from_const_str("Arial")
5027 ))).is_none());
5028 }
5029
5030 #[test]
5031 fn renderer_resources_gc_helper_is_a_noop_on_empty_maps() {
5032 let mut rr = RendererResources::default();
5034 rr.remove_font_families_with_zero_references();
5035 assert!(rr.font_id_map.is_empty());
5036 assert!(rr.font_families_map.is_empty());
5037
5038 let family = StyleFontFamily::System(AzString::from_const_str("Arial"));
5041 let family_hash = StyleFontFamilyHash::new(&family);
5042 let families_hash = StyleFontFamiliesHash::new(core::slice::from_ref(&family));
5043 rr.font_id_map.insert(family_hash, FontKey::unique(IdNamespace(1)));
5044 rr.font_families_map.insert(families_hash, family_hash);
5045 rr.remove_font_families_with_zero_references();
5046 assert!(rr.font_id_map.is_empty(), "dangling font key must be pruned");
5047 assert!(rr.font_families_map.is_empty());
5048 }
5049
5050 #[test]
5051 fn get_font_instance_key_for_text_is_none_on_empty_resources_for_all_sane_sizes() {
5052 let rr = RendererResources::default();
5055 let cache = CssPropertyCache::default();
5056 let node = NodeData::default();
5057 let node_id = NodeId::new(0);
5058 let state = StyledNodeState::default();
5059
5060 for size in [0.0_f32, -0.0, 1.0, -12.0, f32::NAN, 1.0e6, -1.0e6] {
5061 for dpi in [1.0_f32, 0.0, -1.0, f32::NAN, f32::INFINITY] {
5062 assert!(
5063 rr.get_font_instance_key_for_text(size, &cache, &node, &node_id, &state, dpi)
5064 .is_none(),
5065 "size={size} dpi={dpi} must miss cleanly"
5066 );
5067 }
5068 }
5069 }
5070
5071 #[test]
5072 fn bug_get_font_instance_key_for_text_overflow_panics_on_infinite_font_size() {
5073 let rr = RendererResources::default();
5078 let cache = CssPropertyCache::default();
5079 let node = NodeData::default();
5080 let node_id = NodeId::new(0);
5081 let state = StyledNodeState::default();
5082 assert!(rr
5083 .get_font_instance_key_for_text(
5084 f32::INFINITY,
5085 &cache,
5086 &node,
5087 &node_id,
5088 &state,
5089 1.0
5090 )
5091 .is_none());
5092 }
5093
5094 #[test]
5099 fn font_ref_get_hash_is_stable_per_font_and_distinct_across_fonts() {
5100 let a = dummy_font_ref();
5101 let b = dummy_font_ref();
5102 assert_eq!(font_ref_get_hash(&a), font_ref_get_hash(&a));
5103 assert_eq!(font_ref_get_hash(&a), font_ref_get_hash(&a.clone()));
5104 assert_ne!(
5105 font_ref_get_hash(&a),
5106 font_ref_get_hash(&b),
5107 "two distinct FontRefs must not share a hash"
5108 );
5109 }
5110
5111 #[test]
5112 fn build_add_font_resource_updates_on_empty_input_is_empty() {
5113 let mut rr = RendererResources::default();
5114 let fonts = OrderedMap::new();
5115 let updates = build_add_font_resource_updates(
5116 &mut rr,
5117 DpiScaleFactor::new(1.0),
5118 &FcFontCache::default(),
5119 IdNamespace(1),
5120 &fonts,
5121 load_font_none,
5122 parse_font_none,
5123 );
5124 assert!(updates.is_empty());
5125 assert!(rr.font_id_map.is_empty());
5126 }
5127
5128 #[test]
5129 fn build_add_font_resource_updates_skips_unloadable_fonts() {
5130 let mut rr = RendererResources::default();
5133 let mut fonts = OrderedMap::new();
5134 let mut sizes = FastBTreeSet::new();
5135 sizes.insert(Au::from_px(16.0));
5136 fonts.insert(
5137 ImmediateFontId::Unresolved(StyleFontFamilyVec::from_vec(vec![
5138 StyleFontFamily::System(AzString::from_const_str("DoesNotExist")),
5139 ])),
5140 sizes,
5141 );
5142
5143 let updates = build_add_font_resource_updates(
5144 &mut rr,
5145 DpiScaleFactor::new(1.0),
5146 &FcFontCache::default(),
5147 IdNamespace(1),
5148 &fonts,
5149 load_font_none,
5150 parse_font_none,
5151 );
5152 assert!(updates.is_empty(), "an unloadable font must add no resources");
5153 assert!(rr.font_id_map.is_empty());
5154 assert!(rr.font_families_map.is_empty());
5155 }
5156
5157 #[test]
5158 fn build_add_font_resource_updates_registers_a_font_and_deduplicates_sizes() {
5159 let mut rr = RendererResources::default();
5162 let font = dummy_font_ref();
5163 let family = StyleFontFamily::Ref(font.clone());
5164 let dpi = DpiScaleFactor::new(1.0);
5165
5166 let mut sizes = FastBTreeSet::new();
5167 sizes.insert(Au::from_px(16.0));
5168 sizes.insert(Au::from_px(24.0));
5169 sizes.insert(Au::from_px(16.0)); assert_eq!(sizes.len(), 2);
5171
5172 let mut fonts = OrderedMap::new();
5173 fonts.insert(
5174 ImmediateFontId::Unresolved(StyleFontFamilyVec::from_vec(vec![family.clone()])),
5175 sizes,
5176 );
5177
5178 let updates = build_add_font_resource_updates(
5179 &mut rr,
5180 dpi,
5181 &FcFontCache::default(),
5182 IdNamespace(1),
5183 &fonts,
5184 load_font_none,
5185 parse_font_none,
5186 );
5187 assert_eq!(updates.len(), 3);
5189 assert_eq!(
5190 updates
5191 .iter()
5192 .filter(|(_, m)| matches!(m, AddFontMsg::Font(..)))
5193 .count(),
5194 1
5195 );
5196 assert_eq!(
5197 updates
5198 .iter()
5199 .filter(|(_, m)| matches!(m, AddFontMsg::Instance(..)))
5200 .count(),
5201 2
5202 );
5203 assert_eq!(rr.font_id_map.len(), 1);
5204 assert_eq!(rr.font_families_map.len(), 1);
5205
5206 let mut all_updates = Vec::new();
5208 add_resources(&mut rr, &mut all_updates, updates, Vec::new());
5209 assert_eq!(all_updates.len(), 3);
5210
5211 let families_hash = StyleFontFamiliesHash::new(core::slice::from_ref(&family));
5212 assert!(rr
5213 .get_font_instance_key(&families_hash, Au::from_px(16.0), dpi)
5214 .is_some());
5215 assert!(rr
5216 .get_font_instance_key(&families_hash, Au::from_px(24.0), dpi)
5217 .is_some());
5218 assert!(rr
5220 .get_font_instance_key(&families_hash, Au::from_px(99.0), dpi)
5221 .is_none());
5222 assert!(rr
5223 .get_font_instance_key(&families_hash, Au::from_px(16.0), DpiScaleFactor::new(2.0))
5224 .is_none());
5225
5226 let key = rr
5228 .get_font_instance_key(&families_hash, Au::from_px(16.0), dpi)
5229 .expect("registered");
5230 let (font_ref, au, got_dpi) = rr
5231 .get_renderable_font_data(&key)
5232 .expect("registered instance must be renderable");
5233 assert_eq!(font_ref.get_hash(), font.get_hash());
5234 assert_eq!(au, Au::from_px(16.0));
5235 assert_eq!(got_dpi, dpi);
5236
5237 let again = build_add_font_resource_updates(
5239 &mut rr,
5240 dpi,
5241 &FcFontCache::default(),
5242 IdNamespace(1),
5243 &fonts,
5244 load_font_none,
5245 parse_font_none,
5246 );
5247 assert!(again.is_empty(), "already-registered fonts must not be re-added");
5248 }
5249
5250 #[test]
5251 fn add_font_msg_into_resource_update_preserves_keys() {
5252 let font = dummy_font_ref();
5253 let key = FontKey::unique(IdNamespace(3));
5254 let family_hash = StyleFontFamilyHash::new(&StyleFontFamily::Ref(font.clone()));
5255 let msg = AddFontMsg::Font(key, family_hash, font.clone());
5256 match msg.into_resource_update() {
5257 ResourceUpdate::AddFont(add) => {
5258 assert_eq!(add.key, key);
5259 assert_eq!(add.font.get_hash(), font.get_hash());
5260 }
5261 other => panic!("expected AddFont, got {other:?}"),
5262 }
5263 }
5264
5265 #[test]
5266 fn delete_font_msg_into_resource_update_preserves_keys() {
5267 let fk = FontKey::unique(IdNamespace(1));
5268 match DeleteFontMsg::Font(fk).into_resource_update() {
5269 ResourceUpdate::DeleteFont(k) => assert_eq!(k, fk),
5270 other => panic!("expected DeleteFont, got {other:?}"),
5271 }
5272 let fik = FontInstanceKey::unique(IdNamespace(1));
5273 let size = (Au::from_px(16.0), DpiScaleFactor::new(1.0));
5274 match DeleteFontMsg::Instance(fik, size).into_resource_update() {
5275 ResourceUpdate::DeleteFontInstance(k) => assert_eq!(k, fik),
5276 other => panic!("expected DeleteFontInstance, got {other:?}"),
5277 }
5278 }
5279
5280 #[test]
5281 fn add_image_msg_into_resource_update_preserves_the_key_and_descriptor() {
5282 let key = ImageKey::unique(IdNamespace(2));
5283 let descriptor = ImageDescriptor {
5284 format: RawImageFormat::BGRA8,
5285 width: 3,
5286 height: 5,
5287 stride: None.into(),
5288 offset: 0,
5289 flags: ImageDescriptorFlags {
5290 is_opaque: false,
5291 allow_mipmaps: true,
5292 },
5293 };
5294 let msg = AddImageMsg(AddImage {
5295 key,
5296 descriptor,
5297 data: ImageData::Raw(SharedRawImageData::new(vec![0u8; 60].into())),
5298 tiling: None,
5299 });
5300 match msg.into_resource_update() {
5301 ResourceUpdate::AddImage(add) => {
5302 assert_eq!(add.key, key);
5303 assert_eq!(add.descriptor, descriptor);
5304 assert!(add.tiling.is_none());
5305 }
5306 other => panic!("expected AddImage, got {other:?}"),
5307 }
5308 }
5309
5310 #[test]
5311 fn build_add_image_resource_updates_skips_null_and_callback_images() {
5312 let rr = RendererResources::default();
5314 let mut images = FastBTreeSet::new();
5315 images.insert(ImageRef::null_image(4, 4, RawImageFormat::RGBA8, Vec::new()));
5316 images.insert(ImageRef::callback(0usize, RefAny::new(0u8)));
5317
5318 let updates = build_add_image_resource_updates(
5319 &rr,
5320 IdNamespace(1),
5321 Epoch::new(),
5322 &test_document_id(),
5323 &images,
5324 store_gl_texture_noop,
5325 );
5326 assert!(updates.is_empty());
5327
5328 let empty = FastBTreeSet::new();
5330 assert!(build_add_image_resource_updates(
5331 &rr,
5332 IdNamespace(1),
5333 Epoch::new(),
5334 &test_document_id(),
5335 &empty,
5336 store_gl_texture_noop,
5337 )
5338 .is_empty());
5339 }
5340
5341 #[test]
5342 fn build_add_image_resource_updates_then_add_resources_round_trip() {
5343 let mut rr = RendererResources::default();
5344 let img = ImageRef::new_rawimage(rgba8_image(2, 2)).expect("valid 2x2");
5345 let hash = img.get_hash();
5346 let ns = IdNamespace(11);
5347
5348 let mut images = FastBTreeSet::new();
5349 images.insert(img.clone());
5350
5351 let updates = build_add_image_resource_updates(
5352 &rr,
5353 ns,
5354 Epoch::new(),
5355 &test_document_id(),
5356 &images,
5357 store_gl_texture_noop,
5358 );
5359 assert_eq!(updates.len(), 1);
5360 assert_eq!(updates[0].0, hash);
5361 assert_eq!(updates[0].1 .0.key, image_ref_hash_to_image_key(hash, ns));
5363 assert_eq!(updates[0].1 .0.descriptor.width, 2);
5364 assert_eq!(updates[0].1 .0.descriptor.height, 2);
5365
5366 let key = updates[0].1 .0.key;
5367 let mut all_updates = Vec::new();
5368 add_resources(&mut rr, &mut all_updates, Vec::new(), updates);
5369 assert_eq!(all_updates.len(), 1);
5370 assert!(matches!(all_updates[0], ResourceUpdate::AddImage(_)));
5371
5372 assert_eq!(rr.get_image(&hash).map(|r| r.key), Some(key));
5374 assert_eq!(rr.image_key_map.get(&key), Some(&hash));
5375
5376 let again = build_add_image_resource_updates(
5378 &rr,
5379 ns,
5380 Epoch::new(),
5381 &test_document_id(),
5382 &images,
5383 store_gl_texture_noop,
5384 );
5385 assert!(again.is_empty());
5386
5387 let new_descriptor = ImageDescriptor {
5389 format: RawImageFormat::BGRA8,
5390 width: 8,
5391 height: 8,
5392 stride: None.into(),
5393 offset: 0,
5394 flags: ImageDescriptorFlags {
5395 is_opaque: true,
5396 allow_mipmaps: true,
5397 },
5398 };
5399 rr.update_image(&hash, new_descriptor);
5400 assert_eq!(rr.get_image(&hash).map(|r| r.descriptor.width), Some(8));
5401 assert_eq!(rr.get_image(&hash).map(|r| r.key), Some(key));
5402 rr.update_image(&ImageRefHash { inner: u64::MAX }, new_descriptor);
5404 }
5405
5406 #[test]
5407 fn add_resources_with_empty_input_changes_nothing() {
5408 let mut rr = RendererResources::default();
5409 let mut updates = Vec::new();
5410 add_resources(&mut rr, &mut updates, Vec::new(), Vec::new());
5411 assert!(updates.is_empty());
5412 assert!(rr.currently_registered_images.is_empty());
5413 assert!(rr.currently_registered_fonts.is_empty());
5414 assert!(rr.image_key_map.is_empty());
5415 }
5416
5417 #[test]
5422 fn paint_dot_composites_at_the_center_and_leaves_far_pixels_alone() {
5423 let mut img = rgba8_image(4, 4);
5424 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5425 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5426
5427 let idx = (4 + 1) * 4;
5429 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255], "center pixel must be opaque red");
5430 assert_eq!(&px[0..4], &[0, 0, 0, 0], "pixels beyond the radius stay untouched");
5432 }
5433
5434 #[test]
5435 fn paint_dot_honours_bgra_channel_order() {
5436 let mut img = rgba8_image(4, 4);
5437 img.data_format = RawImageFormat::BGRA8;
5438 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5439 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5440 let idx = (4 + 1) * 4;
5441 assert_eq!(&px[idx..idx + 4], &[0, 0, 255, 255]);
5443 }
5444
5445 #[test]
5446 fn paint_dot_rejects_degenerate_radii_and_sizes() {
5447 let untouched = |img: &RawImage| {
5448 img.pixels
5449 .get_u8_vec_ref()
5450 .expect("u8")
5451 .as_ref()
5452 .iter()
5453 .all(|b| *b == 0)
5454 };
5455
5456 for r in [0.0_f32, -1.0, -0.0, f32::NAN, f32::NEG_INFINITY] {
5458 let mut img = rgba8_image(4, 4);
5459 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), r));
5460 assert!(untouched(&img), "radius {r} must not paint");
5461 }
5462
5463 let mut img = rgba8_image(0, 0);
5465 img.paint_dot(0.0, 0.0, Brush::new(opaque_red(), 4.0));
5466 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(0));
5467
5468 for format in [
5470 RawImageFormat::R8,
5471 RawImageFormat::RGB8,
5472 RawImageFormat::RGBA16,
5473 RawImageFormat::RGBAF32,
5474 ] {
5475 let mut img = rgba8_image(4, 4);
5476 img.data_format = format;
5477 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5478 assert!(untouched(&img), "format {format:?} must not be painted");
5479 }
5480 }
5481
5482 #[test]
5483 fn paint_dot_with_nan_and_infinite_coordinates_is_a_safe_noop() {
5484 for (cx, cy) in [
5487 (f32::NAN, 2.0_f32),
5488 (2.0, f32::NAN),
5489 (f32::NAN, f32::NAN),
5490 (f32::INFINITY, 2.0),
5491 (f32::NEG_INFINITY, 2.0),
5492 (2.0, f32::INFINITY),
5493 (1.0e30, 1.0e30),
5494 (-1.0e30, -1.0e30),
5495 ] {
5496 let mut img = rgba8_image(4, 4);
5497 img.paint_dot(cx, cy, Brush::new(opaque_red(), 2.0));
5498 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5499 assert!(
5500 px.iter().all(|b| *b == 0),
5501 "({cx}, {cy}) must not paint anything"
5502 );
5503 }
5504 }
5505
5506 #[test]
5507 fn paint_dot_alpha_saturates_and_never_overflows() {
5508 let mut img = rgba8_image(4, 4);
5510 let brush = Brush::new(opaque_red(), 2.0);
5511 for _ in 0..50 {
5512 img.paint_dot(2.0, 2.0, brush);
5513 }
5514 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5515 let idx = (4 + 1) * 4;
5516 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255]);
5517
5518 let mut img = rgba8_image(4, 4);
5523 let mut nan_brush = Brush::new(opaque_red(), 2.0);
5524 nan_brush.hardness = f32::NAN;
5525 img.paint_dot(2.0, 2.0, nan_brush);
5526 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(64));
5529 }
5530
5531 #[test]
5532 fn paint_dot_zero_flow_and_transparent_color_do_not_paint() {
5533 let mut img = rgba8_image(4, 4);
5534 let mut brush = Brush::new(opaque_red(), 2.0);
5535 brush.flow = 0.0;
5536 img.paint_dot(2.0, 2.0, brush);
5537 assert!(img
5538 .pixels
5539 .get_u8_vec_ref()
5540 .expect("u8")
5541 .as_ref()
5542 .iter()
5543 .all(|b| *b == 0));
5544
5545 let mut img = rgba8_image(4, 4);
5546 let transparent = ColorU {
5547 r: 255,
5548 g: 0,
5549 b: 0,
5550 a: 0,
5551 };
5552 img.paint_dot(2.0, 2.0, Brush::new(transparent, 2.0));
5553 assert!(img
5554 .pixels
5555 .get_u8_vec_ref()
5556 .expect("u8")
5557 .as_ref()
5558 .iter()
5559 .all(|b| *b == 0));
5560
5561 let mut img = rgba8_image(4, 4);
5563 let mut brush = Brush::new(opaque_red(), 2.0);
5564 brush.flow = -5.0;
5565 img.paint_dot(2.0, 2.0, brush);
5566 assert!(img
5567 .pixels
5568 .get_u8_vec_ref()
5569 .expect("u8")
5570 .as_ref()
5571 .iter()
5572 .all(|b| *b == 0));
5573 }
5574
5575 #[test]
5576 fn paint_stroke_paints_both_endpoints() {
5577 let mut img = rgba8_image(8, 8);
5578 img.paint_stroke(1.5, 1.5, 6.5, 6.5, Brush::new(opaque_red(), 1.5));
5579 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5580 let alpha_at = |x: usize, y: usize| px[(y * 8 + x) * 4 + 3];
5581 assert!(alpha_at(1, 1) > 0, "start of the stroke must be painted");
5582 assert!(alpha_at(6, 6) > 0, "end of the stroke must be painted");
5583 assert_eq!(alpha_at(7, 0), 0, "off-line pixels stay untouched");
5584 }
5585
5586 #[test]
5587 fn paint_stroke_zero_length_stamps_a_single_dab() {
5588 let mut img = rgba8_image(4, 4);
5590 img.paint_stroke(2.0, 2.0, 2.0, 2.0, Brush::new(opaque_red(), 2.0));
5591 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5592 let idx = (4 + 1) * 4;
5593 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255]);
5594 }
5595
5596 #[test]
5597 fn paint_stroke_degenerate_brush_params_do_not_divide_by_zero_or_hang() {
5598 for spacing in [0.0_f32, -1.0, f32::NAN] {
5601 let mut img = rgba8_image(8, 8);
5602 let mut brush = Brush::new(opaque_red(), 2.0);
5603 brush.spacing = spacing;
5604 img.paint_stroke(0.0, 0.0, 7.0, 7.0, brush);
5605 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(8 * 8 * 4));
5606 }
5607
5608 let mut img = rgba8_image(4, 4);
5610 img.paint_stroke(f32::NAN, 0.0, 1.0, 1.0, Brush::new(opaque_red(), 1.0));
5611 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(64));
5612
5613 let mut img = rgba8_image(4, 4);
5615 img.paint_stroke(0.0, 0.0, 3.0, 3.0, Brush::new(opaque_red(), 0.0));
5616 assert!(img
5617 .pixels
5618 .get_u8_vec_ref()
5619 .expect("u8")
5620 .as_ref()
5621 .iter()
5622 .all(|b| *b == 0));
5623 }
5624
5625 #[test]
5626 fn bug_paint_stroke_with_infinite_endpoint_loops_2_billion_times() {
5627 let mut img = rgba8_image(4, 4);
5631 img.paint_stroke(0.0, 0.0, f32::INFINITY, 0.0, Brush::new(opaque_red(), 2.0));
5632 }
5633
5634 #[test]
5635 fn bug_paint_dot_indexes_out_of_bounds_when_dims_exceed_the_buffer() {
5636 let mut img = RawImage {
5641 pixels: RawImageData::U8(vec![0u8; 4].into()),
5642 width: 100,
5643 height: 100,
5644 premultiplied_alpha: true,
5645 data_format: RawImageFormat::RGBA8,
5646 tag: Vec::new().into(),
5647 };
5648 img.paint_dot(50.0, 50.0, Brush::new(opaque_red(), 4.0));
5649 }
5650
5651 #[test]
5652 fn bug_into_loaded_image_source_overflows_on_huge_dimensions() {
5653 let img = RawImage {
5657 pixels: RawImageData::U8(vec![0u8; 4].into()),
5658 width: usize::MAX,
5659 height: 2,
5660 premultiplied_alpha: true,
5661 data_format: RawImageFormat::RGBA8,
5662 tag: Vec::new().into(),
5663 };
5664 assert!(img.into_loaded_image_source().is_none());
5665 }
5666}