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 system::SystemStyle,
28 AzString, F32Vec, LayoutDebugMessage, OptionI32, StringVec, U16Vec, U32Vec, U8Vec,
29};
30use rust_fontconfig::FcFontCache;
31
32pub use crate::callbacks::{
34 CoreImageCallback, CoreRenderImageCallback, CoreRenderImageCallbackType,
35};
36use crate::{
37 callbacks::{LayoutCallback, VirtualViewCallback},
38 dom::{DomId, NodeData, NodeType},
39 geom::{LogicalPosition, LogicalRect, LogicalSize},
40 gl::{OptionGlContextPtr, Texture},
41 hit_test::DocumentId,
42 id::NodeId,
43 prop_cache::CssPropertyCache,
44 refany::RefAny,
45 styled_dom::{
46 NodeHierarchyItemId, StyleFontFamiliesHash, StyleFontFamilyHash, StyledDom, StyledNodeState,
47 },
48 ui_solver::GlyphInstance,
49 window::{AzStringPair, OptionChar, StringPairVec},
50 xml::{
51 ComponentDef, ComponentDefVec, ComponentId, ComponentLibrary, ComponentLibraryVec,
52 ComponentSource, RegisterComponentFn, RegisterComponentLibraryFn,
53 },
54 FastBTreeSet, OrderedMap,
55};
56
57#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
63#[repr(C)]
64pub enum UpdateImageType {
65 Background,
67 Content,
69}
70
71#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
72#[repr(C)]
73pub struct DpiScaleFactor {
74 pub inner: FloatValue,
75}
76
77impl DpiScaleFactor {
78 #[must_use] pub fn new(f: f32) -> Self {
79 Self {
80 inner: FloatValue::new(f),
81 }
82 }
83}
84
85#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
87#[repr(C)]
88#[derive(Default)]
89pub enum AppTerminationBehavior {
90 ReturnToMain,
94 RunForever,
97 #[default]
100 EndProcess,
101}
102
103
104#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
107#[repr(C)]
108pub struct NamedFont {
109 pub name: AzString,
111 pub bytes: U8Vec,
113}
114
115impl_option!(
116 NamedFont,
117 OptionNamedFont,
118 copy = false,
119 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
120);
121
122impl NamedFont {
123 #[must_use] pub const fn new(name: AzString, bytes: U8Vec) -> Self {
124 Self { name, bytes }
125 }
126}
127
128impl_vec!(NamedFont, NamedFontVec, NamedFontVecDestructor, NamedFontVecDestructorType, NamedFontVecSlice, OptionNamedFont);
129impl_vec_mut!(NamedFont, NamedFontVec);
130impl_vec_debug!(NamedFont, NamedFontVec);
131impl_vec_partialeq!(NamedFont, NamedFontVec);
132impl_vec_eq!(NamedFont, NamedFontVec);
133impl_vec_partialord!(NamedFont, NamedFontVec);
134impl_vec_ord!(NamedFont, NamedFontVec);
135impl_vec_hash!(NamedFont, NamedFontVec);
136impl_vec_clone!(NamedFont, NamedFontVec, NamedFontVecDestructor);
137
138#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
147#[repr(C)]
148pub struct LoadedFont {
149 pub font_hash: u64,
153 pub family_name: AzString,
156 pub num_glyphs: u32,
158 pub has_bytes: bool,
163}
164
165impl_option!(
166 LoadedFont,
167 OptionLoadedFont,
168 copy = false,
169 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
170);
171
172impl LoadedFont {
173 #[must_use] pub const fn new(font_hash: u64, family_name: AzString, num_glyphs: u32, has_bytes: bool) -> Self {
174 Self {
175 font_hash,
176 family_name,
177 num_glyphs,
178 has_bytes,
179 }
180 }
181}
182
183impl_vec!(LoadedFont, LoadedFontVec, LoadedFontVecDestructor, LoadedFontVecDestructorType, LoadedFontVecSlice, OptionLoadedFont);
184impl_vec_mut!(LoadedFont, LoadedFontVec);
185impl_vec_debug!(LoadedFont, LoadedFontVec);
186impl_vec_partialeq!(LoadedFont, LoadedFontVec);
187impl_vec_eq!(LoadedFont, LoadedFontVec);
188impl_vec_partialord!(LoadedFont, LoadedFontVec);
189impl_vec_ord!(LoadedFont, LoadedFontVec);
190impl_vec_hash!(LoadedFont, LoadedFontVec);
191impl_vec_clone!(LoadedFont, LoadedFontVec, LoadedFontVecDestructor);
192#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
195#[repr(C, u8)]
196#[derive(Default)]
197pub enum FontLoadingConfig {
198 #[default]
200 LoadAllSystemFonts,
201 LoadOnlyFamilies(StringVec),
204 BundledFontsOnly,
206}
207
208
209#[derive(Debug, Clone, Default)]
238#[repr(C)]
239pub struct CssMockEnvironment {
240 pub theme: azul_css::dynamic_selector::OptionThemeCondition,
242 pub language: azul_css::OptionString,
244 pub os_version: azul_css::dynamic_selector::OptionOsVersion,
246 pub os: azul_css::dynamic_selector::OptionOsCondition,
248 pub desktop_env: azul_css::dynamic_selector::OptionLinuxDesktopEnv,
250 pub viewport_width: azul_css::OptionF32,
253 pub viewport_height: azul_css::OptionF32,
254 pub prefers_reduced_motion: azul_css::OptionBool,
256 pub prefers_high_contrast: azul_css::OptionBool,
258}
259
260impl CssMockEnvironment {
261 #[must_use] pub fn linux() -> Self {
263 Self {
264 os: azul_css::dynamic_selector::OptionOsCondition::Some(azul_css::dynamic_selector::OsCondition::Linux),
265 ..Default::default()
266 }
267 }
268
269 #[must_use] pub fn windows() -> Self {
271 Self {
272 os: azul_css::dynamic_selector::OptionOsCondition::Some(azul_css::dynamic_selector::OsCondition::Windows),
273 ..Default::default()
274 }
275 }
276
277 #[must_use] pub fn macos() -> Self {
279 Self {
280 os: azul_css::dynamic_selector::OptionOsCondition::Some(azul_css::dynamic_selector::OsCondition::MacOS),
281 ..Default::default()
282 }
283 }
284
285 #[must_use] pub fn dark_theme() -> Self {
287 Self {
288 theme: azul_css::dynamic_selector::OptionThemeCondition::Some(azul_css::dynamic_selector::ThemeCondition::Dark),
289 ..Default::default()
290 }
291 }
292
293 #[must_use] pub fn light_theme() -> Self {
295 Self {
296 theme: azul_css::dynamic_selector::OptionThemeCondition::Some(azul_css::dynamic_selector::ThemeCondition::Light),
297 ..Default::default()
298 }
299 }
300
301 pub fn apply_to(&self, ctx: &mut azul_css::dynamic_selector::DynamicSelectorContext) {
303 if let azul_css::dynamic_selector::OptionOsCondition::Some(os) = self.os {
304 ctx.os = os;
305 }
306 if let azul_css::dynamic_selector::OptionOsVersion::Some(os_version) = self.os_version {
307 ctx.os_version = os_version;
308 }
309 if let azul_css::dynamic_selector::OptionLinuxDesktopEnv::Some(de) = self.desktop_env {
310 ctx.desktop_env = azul_css::dynamic_selector::OptionLinuxDesktopEnv::Some(de);
311 }
312 if let azul_css::dynamic_selector::OptionThemeCondition::Some(ref theme) = self.theme {
313 ctx.theme = theme.clone();
314 }
315 if let azul_css::OptionString::Some(ref lang) = self.language {
316 ctx.language = lang.clone();
317 }
318 if let azul_css::OptionBool::Some(reduced) = self.prefers_reduced_motion {
319 ctx.prefers_reduced_motion = if reduced {
320 azul_css::dynamic_selector::BoolCondition::True
321 } else {
322 azul_css::dynamic_selector::BoolCondition::False
323 };
324 }
325 if let azul_css::OptionBool::Some(high_contrast) = self.prefers_high_contrast {
326 ctx.prefers_high_contrast = if high_contrast {
327 azul_css::dynamic_selector::BoolCondition::True
328 } else {
329 azul_css::dynamic_selector::BoolCondition::False
330 };
331 }
332 if let azul_css::OptionF32::Some(w) = self.viewport_width {
333 ctx.viewport_width = w;
334 }
335 if let azul_css::OptionF32::Some(h) = self.viewport_height {
336 ctx.viewport_height = h;
337 }
338 }
339}
340
341impl_option!(
342 CssMockEnvironment,
343 OptionCssMockEnvironment,
344 copy = false,
345 [Debug, Clone]
346);
347
348#[repr(C)]
365pub struct Route {
366 pub pattern: AzString,
368 pub layout_callback: LayoutCallback,
370}
371
372impl Clone for Route {
373 fn clone(&self) -> Self {
374 Self { pattern: self.pattern.clone(), layout_callback: self.layout_callback.clone() }
375 }
376}
377impl fmt::Debug for Route {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 f.debug_struct("Route")
380 .field("pattern", &self.pattern)
381 .field("layout_callback", &self.layout_callback)
382 .finish()
383 }
384}
385impl PartialEq for Route { fn eq(&self, o: &Self) -> bool { self.pattern == o.pattern && self.layout_callback == o.layout_callback } }
386impl Eq for Route {}
387impl PartialOrd for Route { fn partial_cmp(&self, o: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(o)) } }
388impl 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)) } }
389impl Hash for Route { fn hash<H: Hasher>(&self, state: &mut H) { self.pattern.hash(state); self.layout_callback.hash(state); } }
390
391impl_option!(Route, OptionRoute, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
392impl_vec!(Route, RouteVec, RouteVecDestructor, RouteVecDestructorType, RouteVecSlice, OptionRoute);
393impl_vec_mut!(Route, RouteVec);
394impl_vec_debug!(Route, RouteVec);
395impl_vec_clone!(Route, RouteVec, RouteVecDestructor);
396impl_vec_partialeq!(Route, RouteVec);
397impl_vec_eq!(Route, RouteVec);
398impl_vec_partialord!(Route, RouteVec);
399impl_vec_ord!(Route, RouteVec);
400impl_vec_hash!(Route, RouteVec);
401
402#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
407#[repr(C)]
408pub struct RouteMatch {
409 pub pattern: AzString,
411 pub params: StringPairVec,
413}
414
415impl RouteMatch {
416 #[must_use] pub fn get_param(&self, key: &str) -> Option<&AzString> {
418 self.params.get_key(key)
419 }
420}
421
422impl_option!(RouteMatch, OptionRouteMatch, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
423
424#[allow(clippy::similar_names)] #[must_use] pub fn match_route(pattern: &str, path: &str) -> Option<RouteMatch> {
434 let pat_segs: Vec<&str> = pattern.split('/').filter(|s| !s.is_empty()).collect();
435 let path_segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
436
437 if pat_segs.len() != path_segs.len() {
438 return None;
439 }
440
441 let mut params = Vec::new();
442 for (pat, val) in pat_segs.iter().zip(path_segs.iter()) {
443 if let Some(param_name) = pat.strip_prefix(':') {
444 params.push(AzStringPair {
445 key: AzString::from(param_name.to_string()),
446 value: AzString::from((*val).to_string()),
447 });
448 } else if pat != val {
449 return None;
450 }
451 }
452
453 Some(RouteMatch {
454 pattern: AzString::from(pattern.to_string()),
455 params: StringPairVec::from_vec(params),
456 })
457}
458
459#[derive(Debug, Clone)]
461#[repr(C)]
462pub struct AppConfig {
463 pub log_level: AppLogLevel,
467 pub enable_visual_panic_hook: bool,
470 pub enable_logging_on_panic: bool,
473 pub termination_behavior: AppTerminationBehavior,
476 pub icon_provider: crate::icon::IconProviderHandle,
480 pub bundled_fonts: NamedFontVec,
483 pub font_loading: FontLoadingConfig,
486 pub mock_css_environment: OptionCssMockEnvironment,
496 pub system_style: SystemStyle,
502 pub component_libraries: ComponentLibraryVec,
512 pub routes: RouteVec,
519}
520
521impl AppConfig {
522 #[must_use] pub fn create() -> Self {
523 let log_level = AppLogLevel::Error;
524 let icon_provider = crate::icon::IconProviderHandle::new();
525 let bundled_fonts = NamedFontVec::from_const_slice(&[]);
526 let font_loading = FontLoadingConfig::default();
527 let system_style = SystemStyle::detect();
528 let mut s = Self {
529 log_level,
530 enable_visual_panic_hook: false,
531 enable_logging_on_panic: true,
532 termination_behavior: AppTerminationBehavior::default(),
533 icon_provider,
534 bundled_fonts,
535 font_loading,
536 mock_css_environment: OptionCssMockEnvironment::None,
537 system_style,
538 component_libraries: ComponentLibraryVec::from_const_slice(&[]),
539 routes: RouteVec::from_const_slice(&[]),
540 };
541 let register_builtin: crate::xml::RegisterComponentLibraryFnType =
546 crate::xml::register_builtin_components;
547 s.add_component_library(
548 AzString::from_const_str("builtin"),
549 register_builtin,
550 );
551 s
552 }
553
554 #[must_use] pub fn with_mock_environment(mut self, env: CssMockEnvironment) -> Self {
571 self.mock_css_environment = OptionCssMockEnvironment::Some(env);
572 self
573 }
574
575 pub fn add_component<R: Into<RegisterComponentFn>>(&mut self, library: AzString, register_fn: R) {
587 let register_fn = register_fn.into();
588 let component = (register_fn.cb)();
589 let empty_libs = ComponentLibraryVec::from_const_slice(&[]);
590 let mut libs = core::mem::replace(&mut self.component_libraries, empty_libs).into_library_owned_vec();
591
592 if let Some(existing_lib) = libs.iter_mut().find(|l| l.name.as_str() == library.as_str()) {
593 let empty_comps = ComponentDefVec::from_const_slice(&[]);
594 let mut comps = core::mem::replace(&mut existing_lib.components, empty_comps).into_library_owned_vec();
595 if let Some(ec) = comps.iter_mut().find(|c| c.id.name.as_str() == component.id.name.as_str()) {
596 *ec = component;
597 } else {
598 comps.push(component);
599 }
600 existing_lib.components = ComponentDefVec::from_vec(comps);
601 } else {
602 libs.push(ComponentLibrary {
603 name: library,
604 version: AzString::from_const_str("1.0.0"),
605 description: AzString::from_const_str(""),
606 components: ComponentDefVec::from_vec(alloc::vec![component]),
607 exportable: true,
608 modifiable: true,
609 data_models: crate::xml::ComponentDataModelVec::from_const_slice(&[]),
610 enum_models: crate::xml::ComponentEnumModelVec::from_const_slice(&[]),
611 });
612 }
613
614 self.component_libraries = ComponentLibraryVec::from_vec(libs);
615 }
616
617 pub fn add_component_library<R: Into<RegisterComponentLibraryFn>>(&mut self, name: AzString, register_fn: R) {
629 let register_fn = register_fn.into();
630 let mut library = (register_fn.cb)();
631 library.name = name;
632
633 let empty_libs = ComponentLibraryVec::from_const_slice(&[]);
634 let mut libs = core::mem::replace(&mut self.component_libraries, empty_libs).into_library_owned_vec();
635 if let Some(existing) = libs.iter_mut().find(|l| l.name.as_str() == library.name.as_str()) {
636 *existing = library;
637 } else {
638 libs.push(library);
639 }
640
641 self.component_libraries = ComponentLibraryVec::from_vec(libs);
642 }
643
644 pub fn add_route<P: Into<AzString>, L: Into<LayoutCallback>>(&mut self, pattern: P, layout_fn: L) {
655 let route = Route {
656 pattern: pattern.into(),
657 layout_callback: layout_fn.into(),
658 };
659 let empty = RouteVec::from_const_slice(&[]);
660 let mut routes = core::mem::replace(&mut self.routes, empty).into_library_owned_vec();
661 if let Some(existing) = routes.iter_mut().find(|r| r.pattern.as_str() == route.pattern.as_str()) {
663 *existing = route;
664 } else {
665 routes.push(route);
666 }
667 self.routes = RouteVec::from_vec(routes);
668 }
669
670 #[must_use] pub fn match_route_for_path(&self, path: &str) -> Option<(&Route, RouteMatch)> {
674 for route in self.routes.as_ref() {
675 if let Some(m) = match_route(route.pattern.as_str(), path) {
676 return Some((route, m));
677 }
678 }
679 None
680 }
681}
682
683impl Default for AppConfig {
684 fn default() -> Self {
685 Self::create()
686 }
687}
688
689#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
690#[repr(C)]
691pub enum AppLogLevel {
692 Off,
693 Error,
694 Warn,
695 Info,
696 Debug,
697 Trace,
698}
699
700#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
702#[repr(C)]
703pub struct ImageDescriptor {
704 pub format: RawImageFormat,
706 pub width: usize,
708 pub height: usize,
709 pub stride: OptionI32,
714 pub offset: i32,
720 pub flags: ImageDescriptorFlags,
722}
723
724#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
726#[repr(C)]
727pub struct ImageDescriptorFlags {
728 pub is_opaque: bool,
731 pub allow_mipmaps: bool,
737}
738
739#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
740pub struct IdNamespace(pub u32);
741
742impl ::core::fmt::Display for IdNamespace {
743 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
744 write!(f, "IdNamespace({})", self.0)
745 }
746}
747
748impl ::core::fmt::Debug for IdNamespace {
749 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
750 write!(f, "{self}")
751 }
752}
753
754#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
755#[repr(C)]
756pub enum RawImageFormat {
757 R8,
758 RG8,
759 RGB8,
760 RGBA8,
761 R16,
762 RG16,
763 RGB16,
764 RGBA16,
765 BGR8,
766 BGRA8,
767 RGBF32,
768 RGBAF32,
769}
770
771static IMAGE_KEY: AtomicU64 = AtomicU64::new(1);
773static FONT_KEY: AtomicU64 = AtomicU64::new(0);
774static FONT_INSTANCE_KEY: AtomicU64 = AtomicU64::new(0);
775
776#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
777pub struct ImageKey {
778 pub namespace: IdNamespace,
779 pub key: u64,
780}
781
782impl ImageKey {
783 pub const DUMMY: Self = Self {
784 namespace: IdNamespace(0),
785 key: 0,
786 };
787
788 pub fn unique(render_api_namespace: IdNamespace) -> Self {
789 Self {
790 namespace: render_api_namespace,
791 key: IMAGE_KEY.fetch_add(1, AtomicOrdering::SeqCst),
792 }
793 }
794}
795
796#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
797pub struct FontKey {
798 pub namespace: IdNamespace,
799 pub key: u64,
800}
801
802impl FontKey {
803 pub fn unique(render_api_namespace: IdNamespace) -> Self {
804 Self {
805 namespace: render_api_namespace,
806 key: FONT_KEY.fetch_add(1, AtomicOrdering::SeqCst),
807 }
808 }
809}
810
811#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
812pub struct FontInstanceKey {
813 pub namespace: IdNamespace,
814 pub key: u64,
815}
816
817impl FontInstanceKey {
818 pub fn unique(render_api_namespace: IdNamespace) -> Self {
819 Self {
820 namespace: render_api_namespace,
821 key: FONT_INSTANCE_KEY.fetch_add(1, AtomicOrdering::SeqCst),
822 }
823 }
824}
825
826#[derive(Debug)]
829pub enum DecodedImage {
830 NullImage {
833 width: usize,
834 height: usize,
835 format: RawImageFormat,
836 tag: Vec<u8>,
838 },
839 Gl(Texture),
841 Raw((ImageDescriptor, ImageData)),
843 Callback(CoreImageCallback),
845 }
850
851#[derive(Debug)]
852#[repr(C)]
853pub struct ImageRef {
854 pub data: *const DecodedImage,
856 pub copies: *const AtomicUsize,
858 pub id: u64,
866 pub run_destructor: bool,
867}
868
869static IMAGE_REF_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
872
873#[must_use]
874fn next_image_ref_id() -> u64 {
875 IMAGE_REF_ID_COUNTER.fetch_add(1, AtomicOrdering::SeqCst)
876}
877
878impl ImageRef {
879 #[must_use] pub const fn get_hash(&self) -> ImageRefHash {
880 image_ref_get_hash(self)
881 }
882}
883
884#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Ord, Eq)]
885#[repr(C)]
886pub struct ImageRefHash {
887 pub inner: u64,
888}
889
890impl_option!(
891 ImageRef,
892 OptionImageRef,
893 copy = false,
894 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
895);
896
897impl ImageRef {
898 #[must_use] pub fn into_inner(self) -> Option<DecodedImage> {
900 unsafe {
905 if self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) == Some(1) {
906 let data = Box::from_raw(self.data.cast_mut());
907 drop(Box::from_raw(self.copies.cast_mut()));
908 core::mem::forget(self); Some(*data)
910 } else {
911 None
912 }
913 }
914 }
915
916 #[must_use] pub const fn get_data(&self) -> &DecodedImage {
917 unsafe { &*self.data }
921 }
922
923 #[must_use] pub fn get_image_callback(&self) -> Option<&CoreImageCallback> {
924 if unsafe { self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) != Some(1) } {
926 return None; }
928
929 match unsafe { &*self.data } {
931 DecodedImage::Callback(gl_texture_callback) => Some(gl_texture_callback),
932 _ => None,
933 }
934 }
935
936 pub fn get_image_callback_mut(&mut self) -> Option<&mut CoreImageCallback> {
937 if unsafe { self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) != Some(1) } {
939 return None; }
941
942 match unsafe { &mut *self.data.cast_mut() } {
945 DecodedImage::Callback(gl_texture_callback) => Some(gl_texture_callback),
946 _ => None,
947 }
948 }
949
950 #[must_use] pub fn deep_copy(&self) -> Self {
952 let new_data = match self.get_data() {
953 DecodedImage::NullImage {
954 width,
955 height,
956 format,
957 tag,
958 } => DecodedImage::NullImage {
959 width: *width,
960 height: *height,
961 format: *format,
962 tag: tag.clone(),
963 },
964 DecodedImage::Gl(tex) => DecodedImage::NullImage {
968 width: tex.size.width as usize,
969 height: tex.size.height as usize,
970 format: tex.format,
971 tag: Vec::new(),
972 },
973 DecodedImage::Raw((descriptor, data)) => {
976 DecodedImage::Raw((*descriptor, data.clone()))
977 }
978 DecodedImage::Callback(cb) => DecodedImage::Callback(cb.clone()),
979 };
980
981 Self::new(new_data)
982 }
983
984 #[must_use] pub const fn is_null_image(&self) -> bool {
985 matches!(self.get_data(), DecodedImage::NullImage { .. })
986 }
987
988 #[must_use] pub const fn is_gl_texture(&self) -> bool {
989 matches!(self.get_data(), DecodedImage::Gl(_))
990 }
991
992 #[must_use] pub const fn is_raw_image(&self) -> bool {
993 matches!(self.get_data(), DecodedImage::Raw((_, _)))
994 }
995
996 #[must_use] pub const fn is_callback(&self) -> bool {
997 matches!(self.get_data(), DecodedImage::Callback(_))
998 }
999
1000 #[must_use] pub fn get_rawimage(&self) -> Option<RawImage> {
1002 match self.get_data() {
1003 DecodedImage::Raw((image_descriptor, image_data)) => Some(RawImage {
1004 pixels: match image_data {
1005 ImageData::Raw(shared_data) => {
1006 let data_clone = shared_data.clone();
1009 data_clone.into_inner().map_or_else(|| RawImageData::U8(shared_data.as_ref().to_vec().into()), RawImageData::U8)
1010 }
1011 ImageData::External(_) => return None,
1012 },
1013 width: image_descriptor.width,
1014 height: image_descriptor.height,
1015 premultiplied_alpha: true,
1016 data_format: image_descriptor.format,
1017 tag: Vec::new().into(),
1018 }),
1019 _ => None,
1020 }
1021 }
1022
1023 #[must_use] pub fn get_bytes(&self) -> Option<&[u8]> {
1026 match self.get_data() {
1027 DecodedImage::Raw((_, image_data)) => match image_data {
1028 ImageData::Raw(shared_data) => Some(shared_data.as_ref()),
1029 ImageData::External(_) => None,
1030 },
1031 _ => None,
1032 }
1033 }
1034
1035 #[must_use] pub fn get_bytes_ptr(&self) -> *const u8 {
1038 match self.get_data() {
1039 DecodedImage::Raw((_, image_data)) => match image_data {
1040 ImageData::Raw(shared_data) => shared_data.as_ptr(),
1041 ImageData::External(_) => core::ptr::null(),
1042 },
1043 _ => core::ptr::null(),
1044 }
1045 }
1046
1047 #[allow(clippy::cast_precision_loss)] #[must_use] pub const fn get_size(&self) -> LogicalSize {
1050 match self.get_data() {
1051 DecodedImage::NullImage { width, height, .. } => {
1052 LogicalSize::new(*width as f32, *height as f32)
1053 }
1054 DecodedImage::Gl(tex) => {
1055 LogicalSize::new(tex.size.width as f32, tex.size.height as f32)
1056 }
1057 DecodedImage::Raw((image_descriptor, _)) => LogicalSize::new(
1058 image_descriptor.width as f32,
1059 image_descriptor.height as f32,
1060 ),
1061 DecodedImage::Callback(_) => LogicalSize::new(0.0, 0.0),
1062 }
1063 }
1064
1065 #[must_use] pub fn null_image(width: usize, height: usize, format: RawImageFormat, tag: Vec<u8>) -> Self {
1066 Self::new(DecodedImage::NullImage {
1067 width,
1068 height,
1069 format,
1070 tag,
1071 })
1072 }
1073
1074 pub fn callback<C: Into<CoreRenderImageCallback>>(callback: C, data: RefAny) -> Self {
1075 Self::new(DecodedImage::Callback(CoreImageCallback {
1076 callback: callback.into(),
1077 refany: data,
1078 }))
1079 }
1080
1081 #[must_use] pub fn new_rawimage(image_data: RawImage) -> Option<Self> {
1082 let (image_data, image_descriptor) = image_data.into_loaded_image_source()?;
1083 Some(Self::new(DecodedImage::Raw((image_descriptor, image_data))))
1084 }
1085
1086 #[must_use] pub fn new_gltexture(texture: Texture) -> Self {
1087 Self::new(DecodedImage::Gl(texture))
1088 }
1089
1090 fn new(data: DecodedImage) -> Self {
1091 Self {
1092 data: Box::into_raw(Box::new(data)),
1093 copies: Box::into_raw(Box::new(AtomicUsize::new(1))),
1094 id: next_image_ref_id(),
1095 run_destructor: true,
1096 }
1097 }
1098
1099 }
1101
1102unsafe impl Send for ImageRef {}
1106unsafe impl Sync for ImageRef {}
1107
1108impl PartialEq for ImageRef {
1113 fn eq(&self, rhs: &Self) -> bool {
1114 self.id == rhs.id
1115 }
1116}
1117
1118impl PartialOrd for ImageRef {
1119 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
1120 Some(self.id.cmp(&other.id))
1121 }
1122}
1123
1124impl Ord for ImageRef {
1125 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1126 self.id.cmp(&other.id)
1127 }
1128}
1129
1130impl Eq for ImageRef {}
1131
1132impl Hash for ImageRef {
1133 fn hash<H>(&self, state: &mut H)
1134 where
1135 H: Hasher,
1136 {
1137 self.id.hash(state);
1138 }
1139}
1140
1141impl Clone for ImageRef {
1142 fn clone(&self) -> Self {
1143 unsafe {
1146 self.copies
1147 .as_ref()
1148 .map(|m| m.fetch_add(1, AtomicOrdering::SeqCst));
1149 }
1150 Self {
1151 data: self.data, copies: self.copies, id: self.id, run_destructor: true,
1155 }
1156 }
1157}
1158
1159impl Drop for ImageRef {
1160 fn drop(&mut self) {
1161 self.run_destructor = false;
1162 unsafe {
1166 let copies = (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst);
1167 if copies == 1 {
1168 drop(Box::from_raw(self.data.cast_mut()));
1169 drop(Box::from_raw(self.copies.cast_mut()));
1170 }
1171 }
1172 }
1173}
1174
1175#[must_use] pub const fn image_ref_get_hash(ir: &ImageRef) -> ImageRefHash {
1176 ImageRefHash {
1182 inner: ir.id,
1183 }
1184}
1185
1186#[must_use] pub const fn image_ref_hash_to_image_key(hash: ImageRefHash, namespace: IdNamespace) -> ImageKey {
1193 ImageKey {
1194 namespace,
1195 key: hash.inner,
1196 }
1197}
1198
1199#[must_use] pub fn font_ref_get_hash(fr: &FontRef) -> u64 {
1200 fr.get_hash()
1201}
1202
1203#[derive(Debug)]
1209#[derive(Default)]
1210pub struct ImageCache {
1211 pub image_id_map: OrderedMap<AzString, ImageRef>,
1217}
1218
1219
1220impl ImageCache {
1221 #[must_use] pub fn new() -> Self {
1222 Self::default()
1223 }
1224
1225 pub fn add_css_image_id(&mut self, css_id: AzString, image: ImageRef) {
1228 self.image_id_map.insert(css_id, image);
1229 }
1230
1231 #[must_use] pub fn get_css_image_id(&self, css_id: &AzString) -> Option<&ImageRef> {
1232 self.image_id_map.get(css_id)
1233 }
1234
1235 pub fn delete_css_image_id(&mut self, css_id: &AzString) {
1236 self.image_id_map.remove(css_id);
1237 }
1238}
1239
1240#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1241pub struct ResolvedImage {
1242 pub key: ImageKey,
1243 pub descriptor: ImageDescriptor,
1244}
1245
1246pub trait RendererResourcesTrait: fmt::Debug {
1248 fn get_font_family(
1250 &self,
1251 style_font_families_hash: &StyleFontFamiliesHash,
1252 ) -> Option<&StyleFontFamilyHash>;
1253
1254 fn get_font_key(&self, style_font_family_hash: &StyleFontFamilyHash) -> Option<&FontKey>;
1256
1257 fn get_registered_font(
1259 &self,
1260 font_key: &FontKey,
1261 ) -> Option<&(FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)>;
1262
1263 fn get_image(&self, hash: &ImageRefHash) -> Option<&ResolvedImage>;
1265
1266 fn update_image(
1268 &mut self,
1269 image_ref_hash: &ImageRefHash,
1270 descriptor: ImageDescriptor,
1271 );
1272}
1273
1274impl RendererResourcesTrait for RendererResources {
1276 fn get_font_family(
1277 &self,
1278 style_font_families_hash: &StyleFontFamiliesHash,
1279 ) -> Option<&StyleFontFamilyHash> {
1280 self.font_families_map.get(style_font_families_hash)
1281 }
1282
1283 fn get_font_key(&self, style_font_family_hash: &StyleFontFamilyHash) -> Option<&FontKey> {
1284 self.font_id_map.get(style_font_family_hash)
1285 }
1286
1287 fn get_registered_font(
1288 &self,
1289 font_key: &FontKey,
1290 ) -> Option<&(FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)> {
1291 self.currently_registered_fonts.get(font_key)
1292 }
1293
1294 fn get_image(&self, hash: &ImageRefHash) -> Option<&ResolvedImage> {
1295 self.currently_registered_images.get(hash)
1296 }
1297
1298 fn update_image(
1299 &mut self,
1300 image_ref_hash: &ImageRefHash,
1301 descriptor: ImageDescriptor,
1302 ) {
1303 if let Some(s) = self.currently_registered_images.get_mut(image_ref_hash) {
1304 s.descriptor = descriptor;
1305 }
1306 }
1307}
1308
1309#[derive(Default)]
1316pub struct RendererResources {
1317 pub currently_registered_images: OrderedMap<ImageRefHash, ResolvedImage>,
1319 pub image_key_map: OrderedMap<ImageKey, ImageRefHash>,
1321 pub image_last_seen_epoch: OrderedMap<ImageRefHash, u32>,
1328 pub currently_registered_fonts:
1330 OrderedMap<FontKey, (FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)>,
1331 pub last_frame_registered_fonts:
1338 OrderedMap<FontKey, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>>,
1339 pub font_families_map: OrderedMap<StyleFontFamiliesHash, StyleFontFamilyHash>,
1344 pub font_id_map: OrderedMap<StyleFontFamilyHash, FontKey>,
1346 pub font_hash_map: OrderedMap<u64, FontKey>,
1349}
1350
1351impl fmt::Debug for RendererResources {
1352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1353 write!(
1354 f,
1355 "RendererResources {{
1356 currently_registered_images: {:#?},
1357 currently_registered_fonts: {:#?},
1358 font_families_map: {:#?},
1359 font_id_map: {:#?},
1360 }}",
1361 self.currently_registered_images.keys().collect::<Vec<_>>(),
1362 self.currently_registered_fonts.keys().collect::<Vec<_>>(),
1363 self.font_families_map.keys().collect::<Vec<_>>(),
1364 self.font_id_map.keys().collect::<Vec<_>>(),
1365 )
1366 }
1367}
1368
1369
1370impl RendererResources {
1371 #[must_use] pub fn get_renderable_font_data(
1372 &self,
1373 font_instance_key: &FontInstanceKey,
1374 ) -> Option<(&FontRef, Au, DpiScaleFactor)> {
1375 self.currently_registered_fonts
1376 .iter()
1377 .find_map(|(font_key, (font_ref, instances))| {
1378 instances.iter().find_map(|((au, dpi), instance_key)| {
1379 if *instance_key == *font_instance_key {
1380 Some((font_ref, *au, *dpi))
1381 } else {
1382 None
1383 }
1384 })
1385 })
1386 }
1387
1388 #[allow(clippy::cast_possible_truncation)] pub fn get_font_instance_key_for_text(
1390 &self,
1391 font_size_px: f32,
1392 css_property_cache: &CssPropertyCache,
1393 node_data: &NodeData,
1394 node_id: &NodeId,
1395 styled_node_state: &StyledNodeState,
1396 dpi_scale: f32,
1397 ) -> Option<FontInstanceKey> {
1398 let font_size = StyleFontSize {
1400 inner: azul_css::props::basic::PixelValue::const_px(font_size_px as isize),
1401 };
1402
1403 let font_size_au = font_size_to_au(font_size);
1405
1406 let dpi_scale_factor = DpiScaleFactor {
1408 inner: FloatValue::new(dpi_scale),
1409 };
1410
1411 let font_family =
1413 css_property_cache.get_font_id_or_default(node_data, node_id, styled_node_state);
1414
1415 let font_families_hash = StyleFontFamiliesHash::new(font_family.as_ref());
1417
1418 self.get_font_instance_key(&font_families_hash, font_size_au, dpi_scale_factor)
1419 }
1420
1421 #[must_use] pub fn get_font_instance_key(
1422 &self,
1423 font_families_hash: &StyleFontFamiliesHash,
1424 font_size_au: Au,
1425 dpi_scale: DpiScaleFactor,
1426 ) -> Option<FontInstanceKey> {
1427 let font_family_hash = self.get_font_family(font_families_hash)?;
1428 let font_key = self.get_font_key(font_family_hash)?;
1429 let (_, instances) = self.get_registered_font(font_key)?;
1430 instances.get(&(font_size_au, dpi_scale)).copied()
1431 }
1432
1433 #[allow(dead_code)]
1464 fn remove_font_families_with_zero_references(&mut self) {
1465 let font_family_to_delete = self
1466 .font_id_map
1467 .iter()
1468 .filter_map(|(font_family, font_key)| {
1469 if self.currently_registered_fonts.contains_key(font_key) {
1470 None
1471 } else {
1472 Some(*font_family)
1473 }
1474 })
1475 .collect::<Vec<_>>();
1476
1477 for f in font_family_to_delete {
1478 self.font_id_map.remove(&f); }
1480
1481 let font_families_to_delete = self
1482 .font_families_map
1483 .iter()
1484 .filter_map(|(font_families, font_family)| {
1485 if self.font_id_map.contains_key(font_family) {
1486 None
1487 } else {
1488 Some(*font_families)
1489 }
1490 })
1491 .collect::<Vec<_>>();
1492
1493 for f in font_families_to_delete {
1494 self.font_families_map.remove(&f); }
1496 }
1497}
1498
1499#[derive(Debug, Clone)]
1510pub struct UpdateImageResult {
1511 pub key_to_update: ImageKey,
1512 pub new_descriptor: ImageDescriptor,
1513 pub new_image_data: ImageData,
1514}
1515
1516#[derive(Debug, Default)]
1517pub struct GlTextureCache {
1518 pub solved_textures:
1519 BTreeMap<DomId, BTreeMap<NodeId, (ImageKey, ImageDescriptor, ExternalImageId)>>,
1520 pub hashes: BTreeMap<(DomId, NodeId, ImageRefHash), ImageRefHash>,
1521}
1522
1523unsafe impl Send for GlTextureCache {}
1528
1529impl GlTextureCache {
1530 #[must_use] pub const fn empty() -> Self {
1532 Self {
1533 solved_textures: BTreeMap::new(),
1534 hashes: BTreeMap::new(),
1535 }
1536 }
1537
1538 pub fn update_texture(
1557 &mut self,
1558 dom_id: DomId,
1559 node_id: NodeId,
1560 document_id: DocumentId,
1561 epoch: Epoch,
1562 new_texture: Texture,
1563 insert_into_active_gl_textures_fn: &GlStoreImageFn,
1564 ) -> Option<ExternalImageId> {
1565 let new_descriptor = new_texture.get_descriptor();
1566 let di_map = self.solved_textures.get_mut(&dom_id)?;
1567 let entry = di_map.get_mut(&node_id)?;
1568
1569 entry.1 = new_descriptor;
1571
1572 let external_image_id = texture_external_image_id(dom_id, node_id);
1575 (insert_into_active_gl_textures_fn)(document_id, epoch, new_texture, external_image_id);
1576 entry.2 = external_image_id;
1577
1578 Some(external_image_id)
1579 }
1580}
1581
1582macro_rules! unique_id {
1583 ($struct_name:ident, $counter_name:ident) => {
1584 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
1585 #[repr(C)]
1586 pub struct $struct_name {
1587 pub id: usize,
1588 }
1589
1590 impl $struct_name {
1591 pub fn unique() -> Self {
1592 Self {
1593 id: $counter_name.fetch_add(1, AtomicOrdering::SeqCst),
1594 }
1595 }
1596 }
1597 };
1598}
1599
1600static PROPERTY_KEY_COUNTER: AtomicUsize = AtomicUsize::new(0);
1602unique_id!(TransformKey, PROPERTY_KEY_COUNTER);
1603unique_id!(ColorKey, PROPERTY_KEY_COUNTER);
1604unique_id!(OpacityKey, PROPERTY_KEY_COUNTER);
1605
1606static IMAGE_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
1607unique_id!(ImageId, IMAGE_ID_COUNTER);
1608static FONT_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
1609unique_id!(FontId, FONT_ID_COUNTER);
1610
1611#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1612#[repr(C)]
1613pub struct ImageMask {
1614 pub image: ImageRef,
1615 pub rect: LogicalRect,
1616 pub repeat: bool,
1617}
1618
1619impl_option!(
1620 ImageMask,
1621 OptionImageMask,
1622 copy = false,
1623 [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
1624);
1625
1626#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1627pub enum ImmediateFontId {
1628 Resolved((StyleFontFamilyHash, FontKey)),
1629 Unresolved(StyleFontFamilyVec),
1630}
1631
1632#[derive(Debug, Clone, PartialEq, PartialOrd)]
1633#[repr(C, u8)]
1634pub enum RawImageData {
1635 U8(U8Vec),
1637 U16(U16Vec),
1639 F32(F32Vec),
1641}
1642
1643impl RawImageData {
1644 #[must_use] pub const fn get_u8_vec_ref(&self) -> Option<&U8Vec> {
1645 match self {
1646 Self::U8(v) => Some(v),
1647 _ => None,
1648 }
1649 }
1650
1651 #[must_use] pub const fn get_u16_vec_ref(&self) -> Option<&U16Vec> {
1652 match self {
1653 Self::U16(v) => Some(v),
1654 _ => None,
1655 }
1656 }
1657
1658 #[must_use] pub const fn get_f32_vec_ref(&self) -> Option<&F32Vec> {
1659 match self {
1660 Self::F32(v) => Some(v),
1661 _ => None,
1662 }
1663 }
1664
1665 fn get_u8_vec(self) -> Option<U8Vec> {
1666 match self {
1667 Self::U8(v) => Some(v),
1668 _ => None,
1669 }
1670 }
1671
1672 fn get_u16_vec(self) -> Option<U16Vec> {
1673 match self {
1674 Self::U16(v) => Some(v),
1675 _ => None,
1676 }
1677 }
1678}
1679
1680#[derive(Debug, Clone, PartialEq, PartialOrd)]
1681#[repr(C)]
1682pub struct RawImage {
1683 pub pixels: RawImageData,
1684 pub width: usize,
1685 pub height: usize,
1686 pub premultiplied_alpha: bool,
1687 pub data_format: RawImageFormat,
1688 pub tag: U8Vec,
1689}
1690
1691#[repr(C)]
1697#[derive(Debug, Copy, Clone, PartialEq)]
1698pub struct Brush {
1699 pub color: ColorU,
1701 pub radius: f32,
1703 pub hardness: f32,
1706 pub flow: f32,
1709 pub spacing: f32,
1712}
1713
1714impl Brush {
1715 #[must_use] pub const fn new(color: ColorU, radius: f32) -> Self {
1717 Self {
1718 color,
1719 radius,
1720 hardness: 0.5,
1721 flow: 1.0,
1722 spacing: 0.25,
1723 }
1724 }
1725}
1726
1727#[allow(clippy::suboptimal_flops)] #[inline]
1734#[must_use] pub fn brush_dab_coverage(t: f32, hardness: f32) -> f32 {
1735 let edge0 = hardness.clamp(0.0, 1.0);
1736 let denom = (1.0 - edge0).max(1.0e-4);
1737 let x = ((t - edge0) / denom).clamp(0.0, 1.0);
1738 1.0 - (x * x * (3.0 - 2.0 * x))
1739}
1740
1741impl RawImage {
1742 #[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) {
1750 let r = brush.radius;
1751 #[allow(clippy::neg_cmp_op_on_partial_ord)]
1753 if !(r > 0.0) || self.width == 0 || self.height == 0 {
1754 return;
1755 }
1756 let bgr = match self.data_format {
1757 RawImageFormat::RGBA8 => false,
1758 RawImageFormat::BGRA8 => true,
1759 _ => return,
1760 };
1761 let (w, h) = (self.width as i32, self.height as i32);
1762 let buf: &mut [u8] = match self.pixels {
1763 RawImageData::U8(ref mut v) => v.as_mut(),
1764 _ => return,
1765 };
1766 let flow = brush.flow.clamp(0.0, 1.0) * (f32::from(brush.color.a) / 255.0);
1767 let (cr, cg, cb) = (
1768 f32::from(brush.color.r),
1769 f32::from(brush.color.g),
1770 f32::from(brush.color.b),
1771 );
1772 let x0 = (cx - r).floor().max(0.0) as i32;
1773 let y0 = (cy - r).floor().max(0.0) as i32;
1774 let x1 = ((cx + r).ceil() as i32).min(w);
1775 let y1 = ((cy + r).ceil() as i32).min(h);
1776 for y in y0..y1 {
1777 for x in x0..x1 {
1778 let dx = x as f32 + 0.5 - cx;
1779 let dy = y as f32 + 0.5 - cy;
1780 let dist = dx.hypot(dy);
1781 if dist > r {
1782 continue;
1783 }
1784 let a = brush_dab_coverage(dist / r, brush.hardness) * flow;
1785 if a <= 0.0 {
1786 continue;
1787 }
1788 let idx = ((y * w + x) as usize) * 4;
1789 let (ri, gi, bi, ai) = if bgr {
1790 (idx + 2, idx + 1, idx, idx + 3)
1791 } else {
1792 (idx, idx + 1, idx + 2, idx + 3)
1793 };
1794 let inv = 1.0 - a;
1795 buf[ri] = (cr * a + f32::from(buf[ri]) * inv).round().clamp(0.0, 255.0) as u8;
1796 buf[gi] = (cg * a + f32::from(buf[gi]) * inv).round().clamp(0.0, 255.0) as u8;
1797 buf[bi] = (cb * a + f32::from(buf[bi]) * inv).round().clamp(0.0, 255.0) as u8;
1798 buf[ai] =
1799 ((a + (f32::from(buf[ai]) / 255.0) * inv) * 255.0).round().clamp(0.0, 255.0) as u8;
1800 }
1801 }
1802 }
1803
1804 #[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) {
1810 let dx = x1 - x0;
1811 let dy = y1 - y0;
1812 let len = dx.hypot(dy);
1813 let step = (brush.radius * brush.spacing.max(0.01)).max(0.5);
1814 let n = (len / step).floor() as i32;
1815 if n <= 0 {
1816 self.paint_dot(x1, y1, brush);
1817 return;
1818 }
1819 for i in 0..=n {
1820 let t = i as f32 / n as f32;
1821 self.paint_dot(x0 + dx * t, y0 + dy * t, brush);
1822 }
1823 }
1824}
1825
1826#[inline]
1831#[allow(clippy::cast_possible_truncation)] fn premultiply_alpha(array: &mut [u8]) {
1833 if array.len() != 4 {
1834 return;
1835 }
1836 let a = u32::from(array[3]);
1837 array[0] = (((u32::from(array[0]) * a) + 128) / 255) as u8;
1838 array[1] = (((u32::from(array[1]) * a) + 128) / 255) as u8;
1839 array[2] = (((u32::from(array[2]) * a) + 128) / 255) as u8;
1840}
1841
1842#[inline]
1843#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] fn normalize_u16(i: u16) -> u8 {
1846 ((f32::from(i) / f32::from(core::u16::MAX)) * f32::from(core::u8::MAX)) as u8
1847}
1848
1849const FOUR_BPP: usize = 4;
1850const TWO_CHANNELS: usize = 2;
1851const THREE_CHANNELS: usize = 3;
1852const FOUR_CHANNELS: usize = 4;
1853
1854impl RawImage {
1855 #[must_use] pub fn null_image() -> Self {
1857 Self {
1858 pixels: RawImageData::U8(Vec::new().into()),
1859 width: 0,
1860 height: 0,
1861 premultiplied_alpha: true,
1862 data_format: RawImageFormat::BGRA8,
1863 tag: Vec::new().into(),
1864 }
1865 }
1866
1867 #[allow(clippy::cast_sign_loss)] #[must_use] pub fn allocate_mask(size: LayoutSize) -> Self {
1870 Self {
1871 pixels: RawImageData::U8(
1872 vec![0; size.width.max(0) as usize * size.height.max(0) as usize].into(),
1873 ),
1874 width: size.width as usize,
1875 height: size.height as usize,
1876 premultiplied_alpha: true,
1877 data_format: RawImageFormat::R8,
1878 tag: Vec::new().into(),
1879 }
1880 }
1881
1882 #[must_use] pub fn into_loaded_image_source(self) -> Option<(ImageData, ImageDescriptor)> {
1888 let Self {
1889 width,
1890 height,
1891 pixels,
1892 data_format,
1893 premultiplied_alpha,
1894 tag,
1895 } = self;
1896
1897 let expected_len = width * height;
1898
1899 let (bytes, data_format, is_opaque): (U8Vec, RawImageFormat, bool) = match data_format {
1900 RawImageFormat::R8 => {
1901 let (bytes, is_opaque) = Self::load_r8(pixels, expected_len)?;
1902 (bytes, RawImageFormat::R8, is_opaque)
1903 }
1904 RawImageFormat::RG8 => {
1905 let (bytes, is_opaque) = Self::load_rg8(pixels, expected_len, premultiplied_alpha)?;
1906 (bytes, RawImageFormat::BGRA8, is_opaque)
1907 }
1908 RawImageFormat::RGB8 => {
1909 let (bytes, is_opaque) = Self::load_rgb8(pixels, expected_len)?;
1910 (bytes, RawImageFormat::BGRA8, is_opaque)
1911 }
1912 RawImageFormat::RGBA8 => {
1913 let (bytes, is_opaque) = Self::load_rgba8(pixels, expected_len, premultiplied_alpha)?;
1914 (bytes, RawImageFormat::BGRA8, is_opaque)
1915 }
1916 RawImageFormat::R16 => {
1917 let (bytes, is_opaque) = Self::load_r16(pixels, expected_len)?;
1918 (bytes, RawImageFormat::BGRA8, is_opaque)
1919 }
1920 RawImageFormat::RG16 => {
1921 let (bytes, is_opaque) = Self::load_rg16(pixels, expected_len)?;
1922 (bytes, RawImageFormat::BGRA8, is_opaque)
1923 }
1924 RawImageFormat::RGB16 => {
1925 let (bytes, is_opaque) = Self::load_rgb16(pixels, expected_len)?;
1926 (bytes, RawImageFormat::BGRA8, is_opaque)
1927 }
1928 RawImageFormat::RGBA16 => {
1929 let (bytes, is_opaque) =
1930 Self::load_rgba16(pixels, expected_len, premultiplied_alpha)?;
1931 (bytes, RawImageFormat::BGRA8, is_opaque)
1932 }
1933 RawImageFormat::BGR8 => {
1934 let (bytes, is_opaque) = Self::load_bgr8(pixels, expected_len)?;
1935 (bytes, RawImageFormat::BGRA8, is_opaque)
1936 }
1937 RawImageFormat::BGRA8 => {
1938 let (bytes, is_opaque) = Self::load_bgra8(pixels, expected_len, premultiplied_alpha)?;
1939 (bytes, RawImageFormat::BGRA8, is_opaque)
1940 }
1941 RawImageFormat::RGBF32 => {
1942 let (bytes, is_opaque) = Self::load_rgbf32(pixels, expected_len)?;
1943 (bytes, RawImageFormat::BGRA8, is_opaque)
1944 }
1945 RawImageFormat::RGBAF32 => {
1946 let (bytes, is_opaque) =
1947 Self::load_rgbaf32(pixels, expected_len, premultiplied_alpha)?;
1948 (bytes, RawImageFormat::BGRA8, is_opaque)
1949 }
1950 };
1951
1952 let image_data = ImageData::Raw(SharedRawImageData::new(bytes));
1953 let image_descriptor = ImageDescriptor {
1954 format: data_format,
1955 width,
1956 height,
1957 offset: 0,
1958 stride: None.into(),
1959 flags: ImageDescriptorFlags {
1960 is_opaque,
1961 allow_mipmaps: true,
1962 },
1963 };
1964
1965 Some((image_data, image_descriptor))
1966 }
1967
1968 fn load_r8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
1972 let pixels = pixels.get_u8_vec()?;
1973
1974 if pixels.len() != expected_len {
1975 return None;
1976 }
1977
1978 Some((pixels, false))
1979 }
1980
1981 fn load_rg8(
1982 pixels: RawImageData,
1983 expected_len: usize,
1984 premultiplied_alpha: bool,
1985 ) -> Option<(U8Vec, bool)> {
1986 let pixels = pixels.get_u8_vec()?;
1987
1988 if pixels.len() != expected_len * TWO_CHANNELS {
1989 return None;
1990 }
1991
1992 let mut is_opaque = true;
1993 let mut px = vec![0; expected_len * FOUR_BPP];
1994
1995 for (pixel_index, greyalpha) in pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate() {
1997 let grey = greyalpha[0];
1998 let alpha = greyalpha[1];
1999
2000 if alpha != 255 {
2001 is_opaque = false;
2002 }
2003
2004 px[pixel_index * FOUR_BPP] = grey;
2005 px[(pixel_index * FOUR_BPP) + 1] = grey;
2006 px[(pixel_index * FOUR_BPP) + 2] = grey;
2007 px[(pixel_index * FOUR_BPP) + 3] = alpha;
2008
2009 if !premultiplied_alpha {
2010 premultiply_alpha(
2011 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2012 );
2013 }
2014 }
2015
2016 Some((px.into(), is_opaque))
2017 }
2018
2019 fn load_rgb8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2020 let pixels = pixels.get_u8_vec()?;
2021
2022 if pixels.len() != expected_len * THREE_CHANNELS {
2023 return None;
2024 }
2025
2026 let mut px = vec![0; expected_len * FOUR_BPP];
2027
2028 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2030 let red = rgb[0];
2031 let green = rgb[1];
2032 let blue = rgb[2];
2033
2034 px[pixel_index * FOUR_BPP] = blue;
2035 px[(pixel_index * FOUR_BPP) + 1] = green;
2036 px[(pixel_index * FOUR_BPP) + 2] = red;
2037 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2038 }
2039
2040 Some((px.into(), true))
2041 }
2042
2043 fn load_rgba8(
2044 pixels: RawImageData,
2045 expected_len: usize,
2046 premultiplied_alpha: bool,
2047 ) -> Option<(U8Vec, bool)> {
2048 let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
2049
2050 if pixels.len() != expected_len * FOUR_CHANNELS {
2051 return None;
2052 }
2053
2054 let mut is_opaque = true;
2055
2056 if premultiplied_alpha {
2059 for rgba in pixels.chunks_exact_mut(4) {
2060 let (r, gba) = rgba.split_first_mut()?;
2061 core::mem::swap(r, gba.get_mut(1)?);
2062 let a = rgba.get_mut(3)?;
2063 if *a != 255 {
2064 is_opaque = false;
2065 }
2066 }
2067 } else {
2068 for rgba in pixels.chunks_exact_mut(4) {
2069 let (r, gba) = rgba.split_first_mut()?;
2071 core::mem::swap(r, gba.get_mut(1)?);
2072 let a = rgba.get_mut(3)?;
2073 if *a != 255 {
2074 is_opaque = false;
2075 }
2076 premultiply_alpha(rgba); }
2078 }
2079
2080 Some((pixels.into(), is_opaque))
2081 }
2082
2083 fn load_r16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2084 let pixels = pixels.get_u16_vec()?;
2085
2086 if pixels.len() != expected_len {
2087 return None;
2088 }
2089
2090 let mut px = vec![0; expected_len * FOUR_BPP];
2091
2092 for (pixel_index, grey_u16) in pixels.as_ref().iter().enumerate() {
2094 let grey_u8 = normalize_u16(*grey_u16);
2095 px[pixel_index * FOUR_BPP] = grey_u8;
2096 px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
2097 px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
2098 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2099 }
2100
2101 Some((px.into(), true))
2102 }
2103
2104 fn load_rg16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2105 let pixels = pixels.get_u16_vec()?;
2106
2107 if pixels.len() != expected_len * TWO_CHANNELS {
2108 return None;
2109 }
2110
2111 let mut is_opaque = true;
2112 let mut px = vec![0; expected_len * FOUR_BPP];
2113
2114 for (pixel_index, greyalpha) in pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate() {
2116 let grey_u8 = normalize_u16(greyalpha[0]);
2117 let alpha_u8 = normalize_u16(greyalpha[1]);
2118
2119 if alpha_u8 != 255 {
2120 is_opaque = false;
2121 }
2122
2123 px[pixel_index * FOUR_BPP] = grey_u8;
2124 px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
2125 px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
2126 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2127 }
2128
2129 Some((px.into(), is_opaque))
2130 }
2131
2132 fn load_rgb16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2133 let pixels = pixels.get_u16_vec()?;
2134
2135 if pixels.len() != expected_len * THREE_CHANNELS {
2136 return None;
2137 }
2138
2139 let mut px = vec![0; expected_len * FOUR_BPP];
2140
2141 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2143 let red_u8 = normalize_u16(rgb[0]);
2144 let green_u8 = normalize_u16(rgb[1]);
2145 let blue_u8 = normalize_u16(rgb[2]);
2146
2147 px[pixel_index * FOUR_BPP] = blue_u8;
2148 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2149 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2150 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2151 }
2152
2153 Some((px.into(), true))
2154 }
2155
2156 fn load_rgba16(
2157 pixels: RawImageData,
2158 expected_len: usize,
2159 premultiplied_alpha: bool,
2160 ) -> Option<(U8Vec, bool)> {
2161 let pixels = pixels.get_u16_vec()?;
2162
2163 if pixels.len() != expected_len * FOUR_CHANNELS {
2164 return None;
2165 }
2166
2167 let mut is_opaque = true;
2168 let mut px = vec![0; expected_len * FOUR_BPP];
2169
2170 if premultiplied_alpha {
2172 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2173 let red_u8 = normalize_u16(rgba[0]);
2174 let green_u8 = normalize_u16(rgba[1]);
2175 let blue_u8 = normalize_u16(rgba[2]);
2176 let alpha_u8 = normalize_u16(rgba[3]);
2177
2178 if alpha_u8 != 255 {
2179 is_opaque = false;
2180 }
2181
2182 px[pixel_index * FOUR_BPP] = blue_u8;
2183 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2184 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2185 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2186 }
2187 } else {
2188 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2189 let red_u8 = normalize_u16(rgba[0]);
2190 let green_u8 = normalize_u16(rgba[1]);
2191 let blue_u8 = normalize_u16(rgba[2]);
2192 let alpha_u8 = normalize_u16(rgba[3]);
2193
2194 if alpha_u8 != 255 {
2195 is_opaque = false;
2196 }
2197
2198 px[pixel_index * FOUR_BPP] = blue_u8;
2199 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2200 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2201 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2202 premultiply_alpha(
2203 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2204 );
2205 }
2206 }
2207
2208 Some((px.into(), is_opaque))
2209 }
2210
2211 fn load_bgr8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2212 let pixels = pixels.get_u8_vec()?;
2213
2214 if pixels.len() != expected_len * THREE_CHANNELS {
2215 return None;
2216 }
2217
2218 let mut px = vec![0; expected_len * FOUR_BPP];
2219
2220 for (pixel_index, bgr) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2222 let blue = bgr[0];
2223 let green = bgr[1];
2224 let red = bgr[2];
2225
2226 px[pixel_index * FOUR_BPP] = blue;
2227 px[(pixel_index * FOUR_BPP) + 1] = green;
2228 px[(pixel_index * FOUR_BPP) + 2] = red;
2229 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2230 }
2231
2232 Some((px.into(), true))
2233 }
2234
2235 fn load_bgra8(
2236 pixels: RawImageData,
2237 expected_len: usize,
2238 premultiplied_alpha: bool,
2239 ) -> Option<(U8Vec, bool)> {
2240 let mut is_opaque = true;
2241
2242 let bytes: U8Vec = if premultiplied_alpha {
2243 let pixels = pixels.get_u8_vec()?;
2245
2246 if pixels.len() != expected_len * FOUR_BPP {
2247 return None;
2248 }
2249
2250 is_opaque = pixels
2251 .as_ref()
2252 .chunks_exact(FOUR_CHANNELS)
2253 .all(|bgra| bgra[3] == 255);
2254
2255 pixels
2256 } else {
2257 let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
2258
2259 if pixels.len() != expected_len * FOUR_BPP {
2260 return None;
2261 }
2262
2263 for bgra in pixels.chunks_exact_mut(FOUR_CHANNELS) {
2264 if bgra[3] != 255 {
2265 is_opaque = false;
2266 }
2267 premultiply_alpha(bgra);
2268 }
2269 pixels.into()
2270 };
2271
2272 Some((bytes, is_opaque))
2273 }
2274
2275 #[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)> {
2279 let pixels = pixels.get_f32_vec_ref()?;
2280
2281 if pixels.len() != expected_len * THREE_CHANNELS {
2282 return None;
2283 }
2284
2285 let mut px = vec![0; expected_len * FOUR_BPP];
2286
2287 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2289 let red_u8 = (rgb[0] * 255.0) as u8;
2290 let green_u8 = (rgb[1] * 255.0) as u8;
2291 let blue_u8 = (rgb[2] * 255.0) as u8;
2292
2293 px[pixel_index * FOUR_BPP] = blue_u8;
2294 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2295 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2296 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2297 }
2298
2299 Some((px.into(), true))
2300 }
2301
2302 #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] #[allow(clippy::needless_pass_by_value)] fn load_rgbaf32(
2306 pixels: RawImageData,
2307 expected_len: usize,
2308 premultiplied_alpha: bool,
2309 ) -> Option<(U8Vec, bool)> {
2310 let pixels = pixels.get_f32_vec_ref()?;
2311
2312 if pixels.len() != expected_len * FOUR_CHANNELS {
2313 return None;
2314 }
2315
2316 let mut is_opaque = true;
2317 let mut px = vec![0; expected_len * FOUR_BPP];
2318
2319 if premultiplied_alpha {
2321 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2322 let red_u8 = (rgba[0] * 255.0) as u8;
2323 let green_u8 = (rgba[1] * 255.0) as u8;
2324 let blue_u8 = (rgba[2] * 255.0) as u8;
2325 let alpha_u8 = (rgba[3] * 255.0) as u8;
2326
2327 if alpha_u8 != 255 {
2328 is_opaque = false;
2329 }
2330
2331 px[pixel_index * FOUR_BPP] = blue_u8;
2332 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2333 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2334 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2335 }
2336 } else {
2337 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2338 let red_u8 = (rgba[0] * 255.0) as u8;
2339 let green_u8 = (rgba[1] * 255.0) as u8;
2340 let blue_u8 = (rgba[2] * 255.0) as u8;
2341 let alpha_u8 = (rgba[3] * 255.0) as u8;
2342
2343 if alpha_u8 != 255 {
2344 is_opaque = false;
2345 }
2346
2347 px[pixel_index * FOUR_BPP] = blue_u8;
2348 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2349 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2350 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2351 premultiply_alpha(
2352 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2353 );
2354 }
2355 }
2356
2357 Some((px.into(), is_opaque))
2358 }
2359}
2360
2361impl_option!(
2362 RawImage,
2363 OptionRawImage,
2364 copy = false,
2365 [Debug, Clone, PartialEq, PartialOrd]
2366);
2367
2368#[must_use] pub fn font_size_to_au(font_size: StyleFontSize) -> Au {
2369 Au::from_px(font_size.inner.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE))
2370}
2371
2372pub type FontInstanceFlags = u32;
2373
2374pub const FONT_INSTANCE_FLAG_SYNTHETIC_BOLD: u32 = 1 << 1;
2376pub const FONT_INSTANCE_FLAG_EMBEDDED_BITMAPS: u32 = 1 << 2;
2377pub const FONT_INSTANCE_FLAG_SUBPIXEL_BGR: u32 = 1 << 3;
2378pub const FONT_INSTANCE_FLAG_TRANSPOSE: u32 = 1 << 4;
2379pub const FONT_INSTANCE_FLAG_FLIP_X: u32 = 1 << 5;
2380pub const FONT_INSTANCE_FLAG_FLIP_Y: u32 = 1 << 6;
2381pub const FONT_INSTANCE_FLAG_SUBPIXEL_POSITION: u32 = 1 << 7;
2382
2383pub const FONT_INSTANCE_FLAG_FORCE_GDI: u32 = 1 << 16;
2385
2386pub const FONT_INSTANCE_FLAG_FONT_SMOOTHING: u32 = 1 << 16;
2388
2389pub const FONT_INSTANCE_FLAG_FORCE_AUTOHINT: u32 = 1 << 16;
2391pub const FONT_INSTANCE_FLAG_NO_AUTOHINT: u32 = 1 << 17;
2392pub const FONT_INSTANCE_FLAG_VERTICAL_LAYOUT: u32 = 1 << 18;
2393pub const FONT_INSTANCE_FLAG_LCD_VERTICAL: u32 = 1 << 19;
2394
2395#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2396pub struct GlyphOptions {
2397 pub render_mode: FontRenderMode,
2398 pub flags: FontInstanceFlags,
2399}
2400
2401#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2402pub enum FontRenderMode {
2403 Mono,
2404 Alpha,
2405 Subpixel,
2406}
2407
2408#[cfg(target_arch = "wasm32")]
2409#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2410pub struct FontInstancePlatformOptions {
2411 }
2413
2414#[cfg(target_os = "windows")]
2415#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2416pub struct FontInstancePlatformOptions {
2417 pub gamma: u16,
2418 pub contrast: u8,
2419 pub cleartype_level: u8,
2420}
2421
2422#[cfg(target_os = "macos")]
2423#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2424pub struct FontInstancePlatformOptions {
2425 pub unused: u32,
2426}
2427
2428#[cfg(target_os = "linux")]
2429#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2430pub struct FontInstancePlatformOptions {
2431 pub lcd_filter: FontLCDFilter,
2432 pub hinting: FontHinting,
2433}
2434
2435#[cfg(any(target_os = "android", target_os = "ios"))]
2439#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2440pub struct FontInstancePlatformOptions {
2441 pub unused: u32,
2442}
2443
2444#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2445pub enum FontHinting {
2446 None,
2447 Mono,
2448 Light,
2449 Normal,
2450 LCD,
2451}
2452
2453#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2454#[derive(Default)]
2455pub enum FontLCDFilter {
2456 None,
2457 #[default]
2458 Default,
2459 Light,
2460 Legacy,
2461}
2462
2463
2464#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2465pub struct FontInstanceOptions {
2466 pub render_mode: FontRenderMode,
2467 pub flags: FontInstanceFlags,
2468 pub bg_color: ColorU,
2469 pub synthetic_italics: SyntheticItalics,
2473}
2474
2475impl Default for FontInstanceOptions {
2476 fn default() -> Self {
2477 Self {
2478 render_mode: FontRenderMode::Subpixel,
2479 flags: 0,
2480 bg_color: ColorU::TRANSPARENT,
2481 synthetic_italics: SyntheticItalics::default(),
2482 }
2483 }
2484}
2485
2486#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2487#[derive(Default)]
2488pub struct SyntheticItalics {
2489 pub angle: i16,
2490}
2491
2492
2493#[derive(Debug)]
2499#[repr(C)]
2500pub struct SharedRawImageData {
2501 pub data: *const U8Vec,
2503 pub copies: *const AtomicUsize,
2505 pub run_destructor: bool,
2507}
2508
2509impl SharedRawImageData {
2510 #[must_use] pub fn new(data: U8Vec) -> Self {
2512 Self {
2513 data: Box::into_raw(Box::new(data)),
2514 copies: Box::into_raw(Box::new(AtomicUsize::new(1))),
2515 run_destructor: true,
2516 }
2517 }
2518
2519 #[must_use] pub fn as_ref(&self) -> &[u8] {
2521 unsafe { (*self.data).as_ref() }
2524 }
2525
2526 #[must_use] pub fn get_bytes(&self) -> &[u8] {
2528 self.as_ref()
2529 }
2530
2531 #[must_use] pub fn as_ptr(&self) -> *const u8 {
2533 unsafe { (*self.data).as_ref().as_ptr() }
2535 }
2536
2537 #[must_use] pub const fn len(&self) -> usize {
2539 unsafe { (*self.data).len() }
2541 }
2542
2543 #[must_use] pub const fn is_empty(&self) -> bool {
2545 self.len() == 0
2546 }
2547
2548 #[must_use] pub fn into_inner(self) -> Option<U8Vec> {
2551 unsafe {
2555 if self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) == Some(1) {
2556 let data = Box::from_raw(self.data.cast_mut());
2557 drop(Box::from_raw(self.copies.cast_mut()));
2558 core::mem::forget(self); Some(*data)
2560 } else {
2561 None
2562 }
2563 }
2564 }
2565}
2566
2567unsafe impl Send for SharedRawImageData {}
2570unsafe impl Sync for SharedRawImageData {}
2571
2572impl Clone for SharedRawImageData {
2573 fn clone(&self) -> Self {
2574 unsafe {
2577 self.copies
2578 .as_ref()
2579 .map(|m| m.fetch_add(1, AtomicOrdering::SeqCst));
2580 }
2581 Self {
2582 data: self.data,
2583 copies: self.copies,
2584 run_destructor: true,
2585 }
2586 }
2587}
2588
2589impl Drop for SharedRawImageData {
2590 fn drop(&mut self) {
2591 self.run_destructor = false;
2592 unsafe {
2596 let copies = (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst);
2597 if copies == 1 {
2598 drop(Box::from_raw(self.data.cast_mut()));
2599 drop(Box::from_raw(self.copies.cast_mut()));
2600 }
2601 }
2602 }
2603}
2604
2605impl PartialEq for SharedRawImageData {
2606 fn eq(&self, rhs: &Self) -> bool {
2607 core::ptr::eq(self.data, rhs.data)
2608 }
2609}
2610
2611impl Eq for SharedRawImageData {}
2612
2613impl PartialOrd for SharedRawImageData {
2614 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
2615 Some(self.cmp(other))
2616 }
2617}
2618
2619impl Ord for SharedRawImageData {
2620 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2621 (self.data as usize).cmp(&(other.data as usize))
2622 }
2623}
2624
2625impl Hash for SharedRawImageData {
2626 fn hash<H>(&self, state: &mut H)
2627 where
2628 H: Hasher,
2629 {
2630 (self.data as usize).hash(state);
2631 }
2632}
2633
2634#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2637#[repr(C, u8)]
2638pub enum ImageData {
2639 Raw(SharedRawImageData),
2642 External(ExternalImageData),
2645}
2646
2647#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
2649#[repr(C, u8)]
2650pub enum ExternalImageType {
2651 TextureHandle(ImageBufferKind),
2653 Buffer,
2655}
2656
2657#[repr(C)]
2661#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
2662pub struct ExternalImageId {
2663 pub inner: u64,
2664}
2665
2666static LAST_EXTERNAL_IMAGE_ID: AtomicUsize = AtomicUsize::new(0);
2667
2668impl Default for ExternalImageId {
2669 fn default() -> Self {
2670 Self::new()
2671 }
2672}
2673
2674impl ExternalImageId {
2675 pub fn new() -> Self {
2677 Self {
2678 inner: LAST_EXTERNAL_IMAGE_ID.fetch_add(1, AtomicOrdering::SeqCst) as u64,
2679 }
2680 }
2681}
2682
2683#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2684#[repr(C, u8)]
2685pub enum GlyphOutlineOperation {
2686 MoveTo(OutlineMoveTo),
2687 LineTo(OutlineLineTo),
2688 QuadraticCurveTo(OutlineQuadTo),
2689 CubicCurveTo(OutlineCubicTo),
2690 ClosePath,
2691}
2692
2693impl_option!(
2694 GlyphOutlineOperation,
2695 OptionGlyphOutlineOperation,
2696 copy = false,
2697 [Debug, Clone, PartialEq, Eq, PartialOrd]
2698);
2699
2700#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2702#[repr(C)]
2703pub struct OutlineMoveTo {
2704 pub x: i16,
2705 pub y: i16,
2706}
2707
2708#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2710#[repr(C)]
2711pub struct OutlineLineTo {
2712 pub x: i16,
2713 pub y: i16,
2714}
2715
2716#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2718#[repr(C)]
2719pub struct OutlineQuadTo {
2720 pub ctrl_1_x: i16,
2721 pub ctrl_1_y: i16,
2722 pub end_x: i16,
2723 pub end_y: i16,
2724}
2725
2726#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2728#[repr(C)]
2729pub struct OutlineCubicTo {
2730 pub ctrl_1_x: i16,
2731 pub ctrl_1_y: i16,
2732 pub ctrl_2_x: i16,
2733 pub ctrl_2_y: i16,
2734 pub end_x: i16,
2735 pub end_y: i16,
2736}
2737
2738#[derive(Debug, Clone, PartialEq, PartialOrd)]
2739#[repr(C)]
2740pub struct GlyphOutline {
2741 pub operations: GlyphOutlineOperationVec,
2742}
2743
2744azul_css::impl_vec!(GlyphOutlineOperation, GlyphOutlineOperationVec, GlyphOutlineOperationVecDestructor, GlyphOutlineOperationVecDestructorType, GlyphOutlineOperationVecSlice, OptionGlyphOutlineOperation);
2745azul_css::impl_vec_clone!(
2746 GlyphOutlineOperation,
2747 GlyphOutlineOperationVec,
2748 GlyphOutlineOperationVecDestructor
2749);
2750azul_css::impl_vec_debug!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2751azul_css::impl_vec_partialord!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2752azul_css::impl_vec_partialeq!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2753
2754#[derive(Debug, Clone, Copy)]
2755#[repr(C)]
2756pub struct OwnedGlyphBoundingBox {
2757 pub max_x: i16,
2758 pub max_y: i16,
2759 pub min_x: i16,
2760 pub min_y: i16,
2761}
2762
2763#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
2765#[repr(C)]
2766pub enum ImageBufferKind {
2767 Texture2D = 0,
2769 TextureRect = 1,
2776 TextureExternal = 2,
2781}
2782
2783#[repr(C)]
2785#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
2786pub struct ExternalImageData {
2787 pub id: ExternalImageId,
2789 pub channel_index: u8,
2792 pub image_type: ExternalImageType,
2794}
2795
2796pub type TileSize = u16;
2797
2798#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
2799pub enum ImageDirtyRect {
2800 All,
2801 Partial(LayoutRect),
2802}
2803
2804#[derive(Debug, Clone, PartialEq, PartialOrd)]
2805pub enum ResourceUpdate {
2806 AddFont(AddFont),
2807 DeleteFont(FontKey),
2808 AddFontInstance(AddFontInstance),
2809 DeleteFontInstance(FontInstanceKey),
2810 AddImage(AddImage),
2811 UpdateImage(UpdateImage),
2812 DeleteImage(ImageKey),
2813}
2814
2815#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2816pub struct AddImage {
2817 pub key: ImageKey,
2818 pub descriptor: ImageDescriptor,
2819 pub data: ImageData,
2820 pub tiling: Option<TileSize>,
2821}
2822
2823#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
2824pub struct UpdateImage {
2825 pub key: ImageKey,
2826 pub descriptor: ImageDescriptor,
2827 pub data: ImageData,
2828 pub dirty_rect: ImageDirtyRect,
2829}
2830
2831#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2834pub struct AddFont {
2835 pub key: FontKey,
2836 pub font: FontRef,
2837}
2838
2839impl fmt::Debug for AddFont {
2840 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2841 write!(
2842 f,
2843 "AddFont {{ key: {:?}, font: {:?} }}",
2844 self.key, self.font
2845 )
2846 }
2847}
2848
2849#[derive(Debug, Clone, PartialEq, PartialOrd)]
2850pub struct AddFontInstance {
2851 pub key: FontInstanceKey,
2852 pub font_key: FontKey,
2853 pub glyph_size: (Au, DpiScaleFactor),
2854 pub options: Option<FontInstanceOptions>,
2855 pub platform_options: Option<FontInstancePlatformOptions>,
2856 pub variations: Vec<FontVariation>,
2857}
2858
2859#[repr(C)]
2860#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
2861pub struct FontVariation {
2862 pub tag: u32,
2863 pub value: f32,
2864}
2865
2866#[repr(C)]
2867#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
2868pub struct Epoch {
2869 inner: u32,
2870}
2871
2872impl fmt::Display for Epoch {
2873 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2874 write!(f, "{}", self.inner)
2875 }
2876}
2877
2878impl Default for Epoch {
2879 fn default() -> Self {
2880 Self::new()
2881 }
2882}
2883
2884impl Epoch {
2885 #[must_use] pub const fn new() -> Self {
2889 Self { inner: 0 }
2890 }
2891 #[must_use] pub const fn from(i: u32) -> Self {
2892 Self { inner: i }
2893 }
2894 #[must_use] pub const fn into_u32(&self) -> u32 {
2895 self.inner
2896 }
2897
2898 pub const fn increment(&mut self) {
2901 use core::u32;
2902 const MAX_ID: u32 = u32::MAX - 1;
2903 *self = match self.inner {
2904 MAX_ID => Self { inner: 0 },
2905 other => Self {
2906 inner: other.saturating_add(1),
2907 },
2908 };
2909 }
2910}
2911
2912#[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Eq, Ord)]
2914pub struct Au(pub i32);
2915
2916pub const AU_PER_PX: i32 = 60;
2917pub const MAX_AU: i32 = (1 << 30) - 1;
2918pub const MIN_AU: i32 = -(1 << 30) - 1;
2919
2920impl Au {
2921 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] #[must_use] pub fn from_px(px: f32) -> Self {
2923 let target_app_units = (px * AU_PER_PX as f32) as i32;
2924 Self(target_app_units.clamp(MIN_AU, MAX_AU))
2925 }
2926 #[allow(clippy::cast_precision_loss)] #[must_use] pub fn into_px(&self) -> f32 {
2928 self.0 as f32 / AU_PER_PX as f32
2929 }
2930}
2931
2932#[derive(Debug)]
2934pub enum AddFontMsg {
2935 Font(FontKey, StyleFontFamilyHash, FontRef),
2937 Instance(AddFontInstance, (Au, DpiScaleFactor)),
2938}
2939
2940impl AddFontMsg {
2941 #[must_use] pub fn into_resource_update(&self) -> ResourceUpdate {
2942 use self::AddFontMsg::{Font, Instance};
2943 match self {
2944 Font(font_key, _, font_ref) => ResourceUpdate::AddFont(AddFont {
2945 key: *font_key,
2946 font: font_ref.clone(),
2947 }),
2948 Instance(fi, _) => ResourceUpdate::AddFontInstance(fi.clone()),
2949 }
2950 }
2951}
2952
2953#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
2954pub enum DeleteFontMsg {
2955 Font(FontKey),
2956 Instance(FontInstanceKey, (Au, DpiScaleFactor)),
2957}
2958
2959impl DeleteFontMsg {
2960 #[must_use] pub const fn into_resource_update(&self) -> ResourceUpdate {
2961 use self::DeleteFontMsg::{Font, Instance};
2962 match self {
2963 Font(f) => ResourceUpdate::DeleteFont(*f),
2964 Instance(fi, _) => ResourceUpdate::DeleteFontInstance(*fi),
2965 }
2966 }
2967}
2968
2969#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
2970pub struct AddImageMsg(pub AddImage);
2971
2972impl AddImageMsg {
2973 #[must_use] pub fn into_resource_update(&self) -> ResourceUpdate {
2974 ResourceUpdate::AddImage(self.0.clone())
2975 }
2976}
2977
2978#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2979#[repr(C)]
2980pub struct LoadedFontSource {
2981 pub data: U8Vec,
2982 pub index: u32,
2983 pub load_outlines: bool,
2984}
2985
2986pub type LoadFontFn = fn(&StyleFontFamily, &FcFontCache) -> Option<LoadedFontSource>;
2988
2989pub type ParseFontFn = fn(LoadedFontSource) -> Option<FontRef>; pub type GlStoreImageFn = fn(DocumentId, Epoch, Texture, ExternalImageId);
2993
2994#[must_use] pub fn texture_external_image_id(dom_id: DomId, node_id: NodeId) -> ExternalImageId {
3001 let dom = dom_id.inner as u64;
3002 let node = node_id.index() as u64;
3003 debug_assert!(u32::try_from(dom).is_ok(), "DomId exceeds 32-bit range");
3004 debug_assert!(u32::try_from(node).is_ok(), "NodeId exceeds 32-bit range");
3005 ExternalImageId {
3006 inner: (dom << 32) | (node & 0xFFFF_FFFF),
3007 }
3008}
3009
3010#[must_use] pub const fn image_ref_hash_to_external_image_id(hash: ImageRefHash) -> ExternalImageId {
3014 ExternalImageId {
3015 inner: hash.inner,
3016 }
3017}
3018
3019#[allow(clippy::too_many_lines)] pub fn build_add_font_resource_updates(
3029 renderer_resources: &mut RendererResources,
3030 dpi: DpiScaleFactor,
3031 fc_cache: &FcFontCache,
3032 id_namespace: IdNamespace,
3033 fonts_in_dom: &OrderedMap<ImmediateFontId, FastBTreeSet<Au>>,
3034 font_source_load_fn: LoadFontFn,
3035 parse_font_fn: ParseFontFn,
3036) -> Vec<(StyleFontFamilyHash, AddFontMsg)> {
3037 let mut resource_updates = Vec::new();
3038 let mut font_instances_added_this_frame = FastBTreeSet::new();
3039
3040 'outer: for (im_font_id, font_sizes) in fonts_in_dom {
3041 macro_rules! insert_font_instances {
3042 ($font_family_hash:expr, $font_key:expr, $font_size:expr) => {{
3043 let font_instance_key_exists = renderer_resources
3044 .currently_registered_fonts
3045 .get(&$font_key)
3046 .and_then(|(_, font_instances)| font_instances.get(&($font_size, dpi)))
3047 .is_some()
3048 || font_instances_added_this_frame.contains(&($font_key, ($font_size, dpi)));
3049
3050 if !font_instance_key_exists {
3051 let font_instance_key = FontInstanceKey::unique(id_namespace);
3052
3053 #[cfg(target_os = "windows")]
3055 let platform_options = FontInstancePlatformOptions {
3056 gamma: 300,
3057 contrast: 100,
3058 cleartype_level: 100,
3059 };
3060
3061 #[cfg(target_os = "linux")]
3062 let platform_options = FontInstancePlatformOptions {
3063 lcd_filter: FontLCDFilter::Default,
3064 hinting: FontHinting::Normal,
3065 };
3066
3067 #[cfg(target_os = "macos")]
3068 let platform_options = FontInstancePlatformOptions::default();
3069
3070 #[cfg(target_arch = "wasm32")]
3071 let platform_options = FontInstancePlatformOptions::default();
3072
3073 #[cfg(any(target_os = "android", target_os = "ios"))]
3074 let platform_options = FontInstancePlatformOptions::default();
3075
3076 let options = FontInstanceOptions {
3077 render_mode: FontRenderMode::Subpixel,
3078 flags: FONT_INSTANCE_FLAG_NO_AUTOHINT,
3079 ..Default::default()
3080 };
3081
3082 font_instances_added_this_frame.insert(($font_key, ($font_size, dpi)));
3083 resource_updates.push((
3084 $font_family_hash,
3085 AddFontMsg::Instance(
3086 AddFontInstance {
3087 key: font_instance_key,
3088 font_key: $font_key,
3089 glyph_size: ($font_size, dpi),
3090 options: Some(options),
3091 platform_options: Some(platform_options),
3092 variations: alloc::vec::Vec::new(),
3093 },
3094 ($font_size, dpi),
3095 ),
3096 ));
3097 }
3098 }};
3099 }
3100
3101 match im_font_id {
3102 ImmediateFontId::Resolved((font_family_hash, font_id)) => {
3103 for font_size in font_sizes {
3106 insert_font_instances!(*font_family_hash, *font_id, *font_size);
3107 }
3108 }
3109 ImmediateFontId::Unresolved(style_font_families) => {
3110 let mut font_family_hash = None;
3120 let font_families_hash = StyleFontFamiliesHash::new(style_font_families.as_ref());
3121
3122 'inner: for family in style_font_families.as_ref() {
3124 let current_family_hash = StyleFontFamilyHash::new(family);
3125
3126 if let Some(font_id) = renderer_resources.font_id_map.get(¤t_family_hash)
3127 {
3128 for font_size in font_sizes {
3130 insert_font_instances!(current_family_hash, *font_id, *font_size);
3131 }
3132 continue 'outer;
3133 }
3134
3135 let font_ref = match family {
3136 StyleFontFamily::Ref(r) => r.clone(), other => {
3138 let Some(font_data) = (font_source_load_fn)(other, fc_cache) else {
3140 continue 'inner;
3141 };
3142
3143
3144
3145 match (parse_font_fn)(font_data) {
3146 Some(s) => s,
3147 None => continue 'inner,
3148 }
3149 }
3150 };
3151
3152 font_family_hash = Some((current_family_hash, font_ref));
3154 break 'inner;
3155 }
3156
3157 let (font_family_hash, font_ref) = match font_family_hash {
3158 None => continue 'outer, Some(s) => s,
3160 };
3161
3162 let font_key = FontKey::unique(id_namespace);
3164 let add_font_msg = AddFontMsg::Font(font_key, font_family_hash, font_ref);
3165
3166 renderer_resources
3167 .font_id_map
3168 .insert(font_family_hash, font_key);
3169 renderer_resources
3170 .font_families_map
3171 .insert(font_families_hash, font_family_hash);
3172 resource_updates.push((font_family_hash, add_font_msg));
3173
3174 for font_size in font_sizes {
3176 insert_font_instances!(font_family_hash, font_key, *font_size);
3177 }
3178 }
3179 }
3180 }
3181
3182 resource_updates
3183}
3184
3185#[allow(unused_variables)]
3201pub fn build_add_image_resource_updates(
3202 renderer_resources: &RendererResources,
3203 id_namespace: IdNamespace,
3204 epoch: Epoch,
3205 document_id: &DocumentId,
3206 images_in_dom: &FastBTreeSet<ImageRef>,
3207 insert_into_active_gl_textures: GlStoreImageFn,
3208) -> Vec<(ImageRefHash, AddImageMsg)> {
3209 images_in_dom
3210 .iter()
3211 .filter_map(|image_ref| {
3212 let image_ref_hash = image_ref_get_hash(image_ref);
3213
3214 if renderer_resources
3215 .currently_registered_images
3216 .contains_key(&image_ref_hash)
3217 {
3218 return None;
3219 }
3220
3221 match image_ref.get_data() {
3224 DecodedImage::Gl(texture) => {
3225 let descriptor = texture.get_descriptor();
3226 let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
3227 let external_image_id = image_ref_hash_to_external_image_id(image_ref_hash);
3231 (insert_into_active_gl_textures)(
3233 *document_id,
3234 epoch,
3235 texture.clone(),
3236 external_image_id,
3237 );
3238 Some((
3239 image_ref_hash,
3240 AddImageMsg(AddImage {
3241 key,
3242 data: ImageData::External(ExternalImageData {
3243 id: external_image_id,
3244 channel_index: 0,
3245 image_type: ExternalImageType::TextureHandle(
3246 ImageBufferKind::Texture2D,
3247 ),
3248 }),
3249 descriptor,
3250 tiling: None,
3251 }),
3252 ))
3253 }
3254 DecodedImage::Raw((descriptor, data)) => {
3255 let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
3256 Some((
3257 image_ref_hash,
3258 AddImageMsg(AddImage {
3259 key,
3260 data: data.clone(), descriptor: *descriptor, tiling: None,
3264 }),
3265 ))
3266 }
3267 DecodedImage::NullImage { .. } | DecodedImage::Callback(_) => None,
3270 }
3271 })
3272 .collect()
3273}
3274
3275#[allow(clippy::needless_pass_by_value)] pub fn add_resources(
3282 renderer_resources: &mut RendererResources,
3283 all_resource_updates: &mut Vec<ResourceUpdate>,
3284 add_font_resources: Vec<(StyleFontFamilyHash, AddFontMsg)>,
3285 add_image_resources: Vec<(ImageRefHash, AddImageMsg)>,
3286) {
3287 all_resource_updates.extend(
3288 add_font_resources
3289 .iter()
3290 .map(|(_, f)| f.into_resource_update()),
3291 );
3292 all_resource_updates.extend(
3293 add_image_resources
3294 .iter()
3295 .map(|(_, i)| i.into_resource_update()),
3296 );
3297
3298 for (image_ref_hash, add_image_msg) in &add_image_resources {
3299 renderer_resources.currently_registered_images.insert(
3300 *image_ref_hash,
3301 ResolvedImage {
3302 key: add_image_msg.0.key,
3303 descriptor: add_image_msg.0.descriptor,
3304 },
3305 );
3306 renderer_resources
3309 .image_key_map
3310 .insert(add_image_msg.0.key, *image_ref_hash);
3311 }
3312
3313 for (_, add_font_msg) in add_font_resources {
3314 use self::AddFontMsg::{Font, Instance};
3315 match add_font_msg {
3316 Font(fk, font_family_hash, font_ref) => {
3317 renderer_resources
3318 .currently_registered_fonts
3319 .entry(fk)
3320 .or_insert_with(|| (font_ref.clone(), OrderedMap::default()));
3321
3322 renderer_resources
3324 .font_hash_map
3325 .insert(font_ref.get_hash(), fk);
3326 }
3327 Instance(fi, size) => {
3328 if let Some((_, instances)) = renderer_resources
3329 .currently_registered_fonts
3330 .get_mut(&fi.font_key)
3331 {
3332 instances.insert(size, fi.key);
3333 }
3334 }
3335 }
3336 }
3337}
3338
3339#[cfg(test)]
3340#[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 {
3342 use super::*;
3343
3344 #[test]
3345 fn normalize_u16_maps_full_range() {
3346 assert_eq!(normalize_u16(0), 0);
3348 assert_eq!(normalize_u16(u16::MAX), 255);
3349 let mid = normalize_u16(u16::MAX / 2);
3351 assert!((126..=128).contains(&mid), "midpoint normalized to {mid}");
3352 assert_eq!(normalize_u16(256), 0);
3355 assert_eq!(normalize_u16(257), 1);
3356 }
3357
3358 #[test]
3359 fn load_bgra8_rejects_wrong_length() {
3360 let short = RawImageData::U8(vec![0u8; 4 * 3].into()); assert!(RawImage::load_bgra8(short, 4, true).is_none());
3364
3365 let ok = RawImageData::U8(vec![255u8; 4 * 4].into()); assert!(RawImage::load_bgra8(ok, 4, true).is_some());
3368
3369 let short2 = RawImageData::U8(vec![0u8; 4 * 2].into());
3371 assert!(RawImage::load_bgra8(short2, 4, false).is_none());
3372 }
3373
3374 #[test]
3377 fn imageref_get_data_reads_backing_box() {
3378 let img = ImageRef::null_image(2, 3, RawImageFormat::RGBA8, vec![7, 8]);
3380 match img.get_data() {
3381 DecodedImage::NullImage { width, height, tag, .. } => {
3382 assert_eq!((*width, *height), (2, 3));
3383 assert_eq!(tag.as_slice(), &[7, 8]);
3384 }
3385 _ => panic!("expected NullImage"),
3386 }
3387 }
3388
3389 #[test]
3390 fn imageref_clone_shares_refcount_and_identity() {
3391 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3394 let c = img.clone();
3395 assert_eq!(img, c); assert!(c.into_inner().is_none());
3398 assert!(img.into_inner().is_some());
3400 }
3401
3402 #[test]
3403 fn imageref_deep_copy_has_distinct_identity() {
3404 let img = ImageRef::null_image(4, 4, RawImageFormat::RGBA8, vec![1]);
3407 let d = img.deep_copy();
3408 assert_ne!(img, d);
3409 drop(img);
3410 assert_eq!(d.get_size().width as usize, 4);
3412 }
3413
3414 #[test]
3415 fn imageref_last_drop_frees_once() {
3416 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3419 let c = img.clone();
3420 drop(img);
3421 drop(c);
3422 }
3423
3424 #[test]
3425 fn imageref_get_callback_none_for_non_callback_and_when_shared() {
3426 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3429 assert!(img.get_image_callback().is_none());
3430 let c = img.clone();
3431 assert!(img.get_image_callback().is_none()); drop(c);
3433 }
3434
3435 #[test]
3436 fn shared_raw_image_data_read_paths() {
3437 let s = SharedRawImageData::new(vec![10u8, 20, 30].into());
3439 assert_eq!(s.as_ref(), &[10, 20, 30]);
3440 assert_eq!(s.len(), 3);
3441 assert!(!s.is_empty());
3442 assert_eq!(unsafe { *s.as_ptr() }, 10);
3443 assert!(SharedRawImageData::new(Vec::<u8>::new().into()).is_empty());
3444 }
3445
3446 #[test]
3447 fn shared_raw_image_data_clone_shares_alloc() {
3448 let s = SharedRawImageData::new(vec![1u8, 2, 3, 4].into());
3451 let c = s.clone();
3452 assert_eq!(s, c); assert_eq!(s.as_ptr(), c.as_ptr());
3454 assert!(c.into_inner().is_none()); let inner = s.into_inner().expect("sole owner extraction");
3456 assert_eq!(inner.as_ref(), &[1, 2, 3, 4]);
3457 }
3458
3459 #[test]
3460 fn shared_raw_image_data_last_drop_frees_once() {
3461 let s = SharedRawImageData::new(vec![0u8; 8].into());
3463 let c = s.clone();
3464 drop(s);
3465 drop(c);
3466 }
3467}
3468
3469#[cfg(test)]
3470#[allow(
3471 clippy::float_cmp,
3472 clippy::items_after_statements,
3473 clippy::redundant_clone,
3474 clippy::cast_possible_truncation,
3475 clippy::cast_precision_loss,
3476 clippy::cast_sign_loss,
3477 clippy::cast_lossless,
3478 clippy::unreadable_literal,
3479 clippy::too_many_lines,
3480 clippy::many_single_char_names,
3481 clippy::similar_names,
3482 unused_qualifications,
3483 unreachable_pub,
3484 private_interfaces
3485)] mod autotest_generated {
3487 use alloc::string::String;
3488
3489 use super::*;
3490
3491 fn dummy_font_ref() -> FontRef {
3500 static DUMMY_FONT_DATA: u8 = 0;
3501 extern "C" fn dummy_destructor(_: *mut core::ffi::c_void) {}
3502 FontRef::new(
3503 core::ptr::addr_of!(DUMMY_FONT_DATA).cast::<core::ffi::c_void>(),
3504 dummy_destructor,
3505 )
3506 }
3507
3508 fn load_font_none(_: &StyleFontFamily, _: &FcFontCache) -> Option<LoadedFontSource> {
3510 None
3511 }
3512
3513 fn parse_font_none(_: LoadedFontSource) -> Option<FontRef> {
3515 None
3516 }
3517
3518 fn store_gl_texture_noop(_: DocumentId, _: Epoch, _: Texture, _: ExternalImageId) {}
3520
3521 fn test_document_id() -> DocumentId {
3522 DocumentId {
3523 namespace_id: IdNamespace(7),
3524 id: 0,
3525 }
3526 }
3527
3528 fn rgba8_image(w: usize, h: usize) -> RawImage {
3530 RawImage {
3531 pixels: RawImageData::U8(vec![0u8; w * h * 4].into()),
3532 width: w,
3533 height: h,
3534 premultiplied_alpha: true,
3535 data_format: RawImageFormat::RGBA8,
3536 tag: Vec::new().into(),
3537 }
3538 }
3539
3540 fn opaque_red() -> ColorU {
3541 ColorU {
3542 r: 255,
3543 g: 0,
3544 b: 0,
3545 a: 255,
3546 }
3547 }
3548
3549 #[test]
3554 fn match_route_valid_minimal_positive_control() {
3555 let m = match_route("/user/:id", "/user/42").expect("documented example must match");
3557 assert_eq!(m.pattern.as_str(), "/user/:id");
3558 assert_eq!(m.get_param("id").map(AzString::as_str), Some("42"));
3559
3560 let root = match_route("/", "/").expect("root must match root");
3561 assert!(root.params.as_ref().is_empty());
3562
3563 assert!(match_route("/about", "/settings").is_none());
3564 }
3565
3566 #[test]
3567 fn match_route_empty_input_does_not_panic() {
3568 let m = match_route("", "").expect("empty vs empty is a zero-segment match");
3571 assert!(m.params.as_ref().is_empty());
3572 assert!(match_route("", "/").is_some()); assert!(match_route("/", "").is_some());
3574 assert!(match_route("", "/a").is_none()); assert!(match_route("/a", "").is_none());
3576 }
3577
3578 #[test]
3579 fn match_route_whitespace_only_is_not_trimmed() {
3580 assert!(match_route(" ", " ").is_some());
3583 assert!(match_route(" ", "\t\n").is_none());
3584 assert!(match_route("/ ", "/").is_none()); assert!(match_route("/\t\n", "/\t\n").is_some());
3586 }
3587
3588 #[test]
3589 fn match_route_garbage_never_panics() {
3590 for pat in [
3591 "\0\0\0",
3592 "///////",
3593 "::::",
3594 ":",
3595 "%%%$#@!",
3596 "\u{feff}",
3597 "/a/../../etc/passwd",
3598 ] {
3599 for path in ["", "/", "\0", "/a/b/c", "%%%$#@!", "\u{feff}"] {
3600 let _ = match_route(pat, path);
3602 }
3603 }
3604 assert!(match_route("///////", "/").is_some());
3606 let m = match_route("/:", "/hello").expect("empty param name still matches");
3608 assert_eq!(m.get_param("").map(AzString::as_str), Some("hello"));
3609 }
3610
3611 #[test]
3612 fn match_route_leading_trailing_junk_is_rejected_or_ignored() {
3613 assert!(match_route("/user/:id/", "/user/42").is_some());
3616 assert!(match_route("/user/:id", "/user/42/").is_some());
3617 assert!(match_route(" /about ", "/about").is_none());
3619 assert!(match_route("/about", "/about;garbage").is_none());
3620 }
3621
3622 #[test]
3623 fn match_route_boundary_number_strings_are_opaque_segments() {
3624 for v in [
3626 "0",
3627 "-0",
3628 "9223372036854775807",
3629 "-9223372036854775808",
3630 "1e400",
3631 "NaN",
3632 "inf",
3633 "-inf",
3634 "0.0000000000000000001",
3635 ] {
3636 let path = String::from("/user/") + v;
3637 let m = match_route("/user/:id", &path).expect("any segment matches a :param");
3638 assert_eq!(m.get_param("id").map(AzString::as_str), Some(v));
3639 }
3640 }
3641
3642 #[test]
3643 fn match_route_unicode_multibyte_does_not_panic() {
3644 let m = match_route("/user/:id", "/user/\u{1F600}").expect("emoji segment matches");
3646 assert_eq!(m.get_param("id").map(AzString::as_str), Some("\u{1F600}"));
3647
3648 let m = match_route("/:é\u{0301}", "/e\u{0301}\u{202E}x").expect("unicode param name");
3650 assert_eq!(
3651 m.get_param("é\u{0301}").map(AzString::as_str),
3652 Some("e\u{0301}\u{202E}x")
3653 );
3654 assert!(match_route("/é", "/e\u{0301}").is_none());
3656 }
3657
3658 #[test]
3659 fn match_route_extremely_long_input_does_not_hang() {
3660 let huge = String::from("/") + &"a".repeat(1_000_000);
3662 assert!(match_route("/x", &huge).is_none());
3663 let m = match_route("/:id", &huge).expect("one long segment is still one segment");
3664 assert_eq!(m.get_param("id").map(|s| s.as_str().len()), Some(1_000_000));
3665 }
3666
3667 #[test]
3668 fn match_route_deeply_nested_input_does_not_stack_overflow() {
3669 let deep = "/a".repeat(10_000);
3671 let m = match_route(&deep, &deep).expect("identical deep paths match");
3672 assert!(m.params.as_ref().is_empty());
3673
3674 let all_params = "/:p".repeat(10_000);
3675 let m = match_route(&all_params, &deep).expect("10k params extract");
3676 assert_eq!(m.params.as_ref().len(), 10_000);
3677 assert_eq!(m.get_param("p").map(AzString::as_str), Some("a"));
3679
3680 let brackets = String::from("/") + &"[".repeat(10_000);
3681 assert!(match_route("/:x", &brackets).is_some());
3682 }
3683
3684 #[test]
3685 fn match_route_segment_count_mismatch_is_none() {
3686 assert!(match_route("/a/:b", "/a").is_none());
3687 assert!(match_route("/a", "/a/b").is_none());
3688 assert!(match_route("/:a/:b/:c", "/1/2").is_none());
3689 }
3690
3691 #[test]
3692 fn route_match_get_param_missing_keys_return_none() {
3693 let empty = RouteMatch {
3694 pattern: AzString::from_const_str("/"),
3695 params: StringPairVec::from_vec(Vec::new()),
3696 };
3697 assert!(empty.get_param("").is_none());
3699 assert!(empty.get_param(" ").is_none());
3700 assert!(empty.get_param("\t\n").is_none());
3701 assert!(empty.get_param("\u{1F600}").is_none());
3702 assert!(empty.get_param("\0").is_none());
3703 assert!(empty.get_param(&"k".repeat(100_000)).is_none());
3704
3705 let m = match_route("/u/:id", "/u/7").expect("valid");
3707 assert_eq!(m.get_param("id").map(AzString::as_str), Some("7"));
3708 assert!(m.get_param("ID").is_none()); assert!(m.get_param("i").is_none()); assert!(m.get_param(":id").is_none()); }
3712
3713 #[test]
3714 fn app_config_match_route_for_path_adversarial_inputs() {
3715 let mut config = AppConfig::create();
3716 let cb: crate::callbacks::LayoutCallbackType = autotest_layout;
3717 extern "C" fn autotest_layout(
3718 _: RefAny,
3719 _: crate::callbacks::LayoutCallbackInfo,
3720 ) -> crate::dom::Dom {
3721 crate::dom::Dom::create_body()
3722 }
3723 config.add_route(AzString::from_const_str("/"), cb);
3724 config.add_route(AzString::from_const_str("/user/:id"), cb);
3725
3726 let (route, m) = config
3728 .match_route_for_path("/user/42")
3729 .expect("registered route must match");
3730 assert_eq!(route.pattern.as_str(), "/user/:id");
3731 assert_eq!(m.get_param("id").map(AzString::as_str), Some("42"));
3732
3733 assert!(config.match_route_for_path("").is_some());
3735 assert!(config.match_route_for_path("/").is_some());
3736
3737 assert!(config.match_route_for_path("/nope/nope/nope").is_none());
3739 assert!(config.match_route_for_path("\0\0").is_none());
3740 assert!(config.match_route_for_path(" ").is_none());
3741 let long = String::from("/user/") + &"9".repeat(500_000);
3742 assert!(config.match_route_for_path(&long).is_some());
3743 let m = config
3744 .match_route_for_path("/user/\u{1F600}")
3745 .expect("unicode param");
3746 assert_eq!(m.1.get_param("id").map(AzString::as_str), Some("\u{1F600}"));
3747 }
3748
3749 #[test]
3750 fn app_config_add_route_replaces_same_pattern_and_orders_by_insertion() {
3751 extern "C" fn layout_a(_: RefAny, _: crate::callbacks::LayoutCallbackInfo) -> crate::dom::Dom {
3752 crate::dom::Dom::create_body()
3753 }
3754 let cb: crate::callbacks::LayoutCallbackType = layout_a;
3755
3756 let mut config = AppConfig::create();
3757 assert!(config.routes.as_ref().is_empty());
3758 config.add_route(AzString::from_const_str("/dup"), cb);
3759 config.add_route(AzString::from_const_str("/dup"), cb);
3760 assert_eq!(config.routes.as_ref().len(), 1, "same pattern must replace");
3761
3762 let mut config = AppConfig::create();
3764 config.add_route(AzString::from_const_str("/:anything"), cb);
3765 config.add_route(AzString::from_const_str("/about"), cb);
3766 let (route, _) = config.match_route_for_path("/about").expect("matches");
3767 assert_eq!(route.pattern.as_str(), "/:anything");
3768 }
3769
3770 #[test]
3775 fn dpi_scale_factor_new_handles_nan_and_infinities() {
3776 assert_eq!(DpiScaleFactor::new(0.0).inner.get(), 0.0);
3778 assert_eq!(DpiScaleFactor::new(1.0).inner.get(), 1.0);
3779 assert_eq!(DpiScaleFactor::new(f32::NAN).inner.get(), 0.0);
3780 assert!(DpiScaleFactor::new(f32::INFINITY).inner.get().is_finite());
3781 assert!(DpiScaleFactor::new(f32::NEG_INFINITY).inner.get().is_finite());
3782 assert!(DpiScaleFactor::new(f32::MAX).inner.get().is_finite());
3783 assert!(DpiScaleFactor::new(f32::MIN).inner.get().is_finite());
3784 assert_eq!(DpiScaleFactor::new(f32::MIN_POSITIVE).inner.get(), 0.0);
3786 assert_eq!(DpiScaleFactor::new(1.5), DpiScaleFactor::new(1.5));
3788 assert_ne!(DpiScaleFactor::new(1.5), DpiScaleFactor::new(2.0));
3789 }
3790
3791 #[test]
3792 fn named_font_new_keeps_fields_verbatim() {
3793 let f = NamedFont::new(
3794 AzString::from_const_str(""),
3795 U8Vec::from_vec(Vec::new()),
3796 );
3797 assert_eq!(f.name.as_str(), "");
3798 assert!(f.bytes.as_ref().is_empty());
3799
3800 let bytes = vec![0u8, 255, 128];
3801 let f = NamedFont::new(AzString::from(String::from("\u{1F600}")), bytes.clone().into());
3802 assert_eq!(f.name.as_str(), "\u{1F600}");
3803 assert_eq!(f.bytes.as_ref(), bytes.as_slice());
3804 }
3805
3806 #[test]
3807 fn loaded_font_new_keeps_fields_verbatim_at_limits() {
3808 let f = LoadedFont::new(0, AzString::from_const_str(""), 0, false);
3809 assert_eq!(f.font_hash, 0);
3810 assert_eq!(f.num_glyphs, 0);
3811 assert!(!f.has_bytes);
3812
3813 let f = LoadedFont::new(u64::MAX, AzString::from_const_str("x"), u32::MAX, true);
3814 assert_eq!(f.font_hash, u64::MAX);
3815 assert_eq!(f.num_glyphs, u32::MAX);
3816 assert!(f.has_bytes);
3817 }
3818
3819 #[test]
3820 fn brush_new_defaults_and_extreme_radius() {
3821 let b = Brush::new(opaque_red(), 4.0);
3822 assert_eq!(b.radius, 4.0);
3823 assert_eq!(b.hardness, 0.5);
3824 assert_eq!(b.flow, 1.0);
3825 assert_eq!(b.spacing, 0.25);
3826 assert_eq!(b.color, opaque_red());
3827
3828 assert!(Brush::new(opaque_red(), f32::NAN).radius.is_nan());
3830 assert_eq!(Brush::new(opaque_red(), -0.0).radius, -0.0);
3831 assert_eq!(Brush::new(opaque_red(), f32::INFINITY).radius, f32::INFINITY);
3832 }
3833
3834 #[test]
3835 fn image_cache_new_is_empty_and_default_is_neutral() {
3836 let cache = ImageCache::new();
3837 assert!(cache.image_id_map.is_empty());
3838 assert!(ImageCache::default().image_id_map.is_empty());
3839 }
3840
3841 #[test]
3842 fn gl_texture_cache_empty_is_neutral() {
3843 let cache = GlTextureCache::empty();
3844 assert!(cache.solved_textures.is_empty());
3845 assert!(cache.hashes.is_empty());
3846 let d = GlTextureCache::default();
3847 assert!(d.solved_textures.is_empty());
3848 assert!(d.hashes.is_empty());
3849 }
3850
3851 #[test]
3852 fn external_image_id_new_is_monotonic() {
3853 let a = ExternalImageId::new();
3854 let b = ExternalImageId::new();
3855 assert!(b.inner > a.inner, "the counter must strictly increase");
3856 assert!(ExternalImageId::default().inner > b.inner);
3857 }
3858
3859 #[test]
3860 fn shared_raw_image_data_new_invariants() {
3861 let empty = SharedRawImageData::new(U8Vec::from_vec(Vec::new()));
3862 assert_eq!(empty.len(), 0);
3863 assert!(empty.is_empty());
3864 assert!(empty.as_ref().is_empty());
3865 assert!(empty.get_bytes().is_empty());
3866 assert!(!empty.as_ptr().is_null());
3868
3869 let big = SharedRawImageData::new(vec![7u8; 100_000].into());
3870 assert_eq!(big.len(), 100_000);
3871 assert!(!big.is_empty());
3872 assert_eq!(big.as_ref().len(), big.len());
3873 assert_eq!(big.get_bytes(), big.as_ref());
3874 assert_eq!(big.into_inner().expect("sole owner").as_ref().len(), 100_000);
3876 }
3877
3878 #[test]
3879 fn app_config_create_registers_builtins_and_defaults() {
3880 let config = AppConfig::create();
3881 assert_eq!(config.log_level, AppLogLevel::Error);
3882 assert!(!config.enable_visual_panic_hook);
3883 assert!(config.enable_logging_on_panic);
3884 assert_eq!(config.termination_behavior, AppTerminationBehavior::EndProcess);
3885 assert!(config.routes.as_ref().is_empty());
3886 assert!(matches!(
3887 config.mock_css_environment,
3888 OptionCssMockEnvironment::None
3889 ));
3890 let libs = config.component_libraries.as_ref();
3892 assert_eq!(libs.len(), 1);
3893 assert_eq!(libs[0].name.as_str(), "builtin");
3894 assert!(!libs[0].components.as_ref().is_empty());
3895 }
3896
3897 #[test]
3898 fn app_config_add_component_library_replaces_same_name() {
3899 let register: crate::xml::RegisterComponentLibraryFnType =
3900 crate::xml::register_builtin_components;
3901 let mut config = AppConfig::create();
3902 let n_builtin = config.component_libraries.as_ref()[0].components.as_ref().len();
3903
3904 config.add_component_library(AzString::from_const_str("builtin"), register);
3906 assert_eq!(config.component_libraries.as_ref().len(), 1);
3907 assert_eq!(
3908 config.component_libraries.as_ref()[0].components.as_ref().len(),
3909 n_builtin
3910 );
3911
3912 config.add_component_library(AzString::from_const_str(""), register);
3914 config.add_component_library(AzString::from_const_str("\u{1F600}"), register);
3915 assert_eq!(config.component_libraries.as_ref().len(), 3);
3916 assert_eq!(config.component_libraries.as_ref()[2].name.as_str(), "\u{1F600}");
3917 }
3918
3919 #[test]
3920 fn app_config_with_mock_environment_sets_the_option() {
3921 let config = AppConfig::create().with_mock_environment(CssMockEnvironment::dark_theme());
3922 match config.mock_css_environment {
3923 OptionCssMockEnvironment::Some(env) => {
3924 assert!(matches!(
3925 env.theme,
3926 azul_css::dynamic_selector::OptionThemeCondition::Some(
3927 azul_css::dynamic_selector::ThemeCondition::Dark
3928 )
3929 ));
3930 }
3931 OptionCssMockEnvironment::None => panic!("mock env must be Some"),
3932 }
3933 let config = AppConfig::create()
3935 .with_mock_environment(CssMockEnvironment::linux())
3936 .with_mock_environment(CssMockEnvironment::windows());
3937 match config.mock_css_environment {
3938 OptionCssMockEnvironment::Some(env) => assert!(matches!(
3939 env.os,
3940 azul_css::dynamic_selector::OptionOsCondition::Some(
3941 azul_css::dynamic_selector::OsCondition::Windows
3942 )
3943 )),
3944 OptionCssMockEnvironment::None => panic!("mock env must be Some"),
3945 }
3946 }
3947
3948 #[test]
3953 fn css_mock_environment_presets_only_set_their_own_field() {
3954 use azul_css::dynamic_selector::{
3955 OptionOsCondition, OptionThemeCondition, OsCondition, ThemeCondition,
3956 };
3957
3958 for (mock, os) in [
3959 (CssMockEnvironment::linux(), OsCondition::Linux),
3960 (CssMockEnvironment::windows(), OsCondition::Windows),
3961 (CssMockEnvironment::macos(), OsCondition::MacOS),
3962 ] {
3963 assert!(matches!(mock.os, OptionOsCondition::Some(o) if o == os));
3964 assert!(matches!(mock.theme, OptionThemeCondition::None));
3966 assert!(matches!(mock.viewport_width, azul_css::OptionF32::None));
3967 }
3968
3969 assert!(matches!(
3970 CssMockEnvironment::dark_theme().theme,
3971 OptionThemeCondition::Some(ThemeCondition::Dark)
3972 ));
3973 assert!(matches!(
3974 CssMockEnvironment::light_theme().theme,
3975 OptionThemeCondition::Some(ThemeCondition::Light)
3976 ));
3977 assert!(matches!(
3978 CssMockEnvironment::dark_theme().os,
3979 OptionOsCondition::None
3980 ));
3981 }
3982
3983 #[test]
3984 fn css_mock_environment_apply_to_overrides_only_set_fields() {
3985 use azul_css::dynamic_selector::{
3986 BoolCondition, DynamicSelectorContext, OptionOsCondition, OptionThemeCondition,
3987 OsCondition, ThemeCondition,
3988 };
3989
3990 let mut ctx = DynamicSelectorContext::default();
3992 let before_os = ctx.os;
3993 let before_lang = ctx.language.clone();
3994 let before_w = ctx.viewport_width;
3995 CssMockEnvironment::default().apply_to(&mut ctx);
3996 assert_eq!(ctx.os, before_os);
3997 assert_eq!(ctx.language.as_str(), before_lang.as_str());
3998 assert_eq!(ctx.viewport_width, before_w);
3999
4000 let mock = CssMockEnvironment {
4003 os: OptionOsCondition::Some(OsCondition::Windows),
4004 theme: OptionThemeCondition::Some(ThemeCondition::Dark),
4005 language: azul_css::OptionString::Some(AzString::from_const_str("de-DE")),
4006 viewport_width: azul_css::OptionF32::Some(f32::NAN),
4007 viewport_height: azul_css::OptionF32::Some(f32::INFINITY),
4008 prefers_reduced_motion: azul_css::OptionBool::Some(true),
4009 prefers_high_contrast: azul_css::OptionBool::Some(false),
4010 ..Default::default()
4011 };
4012 let mut ctx = DynamicSelectorContext::default();
4013 mock.apply_to(&mut ctx);
4014 assert_eq!(ctx.os, OsCondition::Windows);
4015 assert_eq!(ctx.theme, ThemeCondition::Dark);
4016 assert_eq!(ctx.language.as_str(), "de-DE");
4017 assert!(ctx.viewport_width.is_nan());
4018 assert_eq!(ctx.viewport_height, f32::INFINITY);
4019 assert_eq!(ctx.prefers_reduced_motion, BoolCondition::True);
4020 assert_eq!(ctx.prefers_high_contrast, BoolCondition::False);
4021
4022 let mut ctx2 = ctx.clone();
4024 mock.apply_to(&mut ctx2);
4025 assert_eq!(ctx2.os, ctx.os);
4026 assert_eq!(ctx2.theme, ctx.theme);
4027 }
4028
4029 #[test]
4034 fn brush_dab_coverage_boundaries_and_monotonicity() {
4035 assert_eq!(brush_dab_coverage(0.0, 0.5), 1.0);
4037 assert_eq!(brush_dab_coverage(1.0, 0.5), 0.0);
4038 assert_eq!(brush_dab_coverage(-5.0, 0.5), 1.0);
4040 assert_eq!(brush_dab_coverage(2.0, 0.5), 0.0);
4041 assert_eq!(brush_dab_coverage(f32::INFINITY, 0.5), 0.0);
4042 assert_eq!(brush_dab_coverage(f32::NEG_INFINITY, 0.5), 1.0);
4043
4044 let mut prev = f32::INFINITY;
4046 for i in 0..=100 {
4047 let t = i as f32 / 100.0;
4048 let c = brush_dab_coverage(t, 0.5);
4049 assert!((0.0..=1.0).contains(&c), "coverage {c} out of range at t={t}");
4050 assert!(c <= prev + 1.0e-6, "not monotonic at t={t}");
4051 prev = c;
4052 }
4053 }
4054
4055 #[test]
4056 fn brush_dab_coverage_hardness_limits_never_divide_by_zero() {
4057 assert_eq!(brush_dab_coverage(0.5, 1.0), 1.0);
4060 assert!(brush_dab_coverage(1.0, 1.0).is_finite());
4061 assert_eq!(brush_dab_coverage(1.0, 1.0), 1.0); assert_eq!(brush_dab_coverage(2.0, 1.0), 0.0);
4063
4064 assert_eq!(brush_dab_coverage(0.5, -10.0), brush_dab_coverage(0.5, 0.0));
4066 assert_eq!(brush_dab_coverage(0.5, 10.0), brush_dab_coverage(0.5, 1.0));
4067 assert_eq!(
4068 brush_dab_coverage(0.5, f32::NEG_INFINITY),
4069 brush_dab_coverage(0.5, 0.0)
4070 );
4071 assert!(brush_dab_coverage(0.5, f32::INFINITY).is_finite());
4072 }
4073
4074 #[test]
4075 fn brush_dab_coverage_nan_propagates_without_panicking() {
4076 assert!(brush_dab_coverage(f32::NAN, 0.5).is_nan());
4079 assert!(brush_dab_coverage(0.5, f32::NAN).is_nan());
4080 assert!(brush_dab_coverage(f32::NAN, f32::NAN).is_nan());
4081 }
4082
4083 #[test]
4084 fn normalize_u16_is_monotonic_and_saturating() {
4085 assert_eq!(normalize_u16(u16::MIN), 0);
4086 assert_eq!(normalize_u16(u16::MAX), u8::MAX);
4087 let mut prev = 0u8;
4088 for i in (0..=u16::MAX).step_by(97) {
4089 let v = normalize_u16(i);
4090 assert!(v >= prev, "normalize_u16 must be monotonic ({i} -> {v})");
4091 prev = v;
4092 }
4093 assert!(normalize_u16(u16::MAX - 1) <= u8::MAX);
4095 }
4096
4097 #[test]
4098 fn premultiply_alpha_ignores_non_4_byte_slices() {
4099 for len in [0usize, 1, 2, 3, 5, 8] {
4101 let mut buf = vec![200u8; len];
4102 let before = buf.clone();
4103 premultiply_alpha(&mut buf);
4104 assert_eq!(buf, before, "len {len} must be left untouched");
4105 }
4106 }
4107
4108 #[test]
4109 fn premultiply_alpha_boundary_values_never_overflow() {
4110 let mut opaque = [255u8, 128, 0, 255];
4112 premultiply_alpha(&mut opaque);
4113 assert_eq!(opaque, [255, 128, 0, 255]);
4114
4115 let mut transparent = [255u8, 255, 255, 0];
4117 premultiply_alpha(&mut transparent);
4118 assert_eq!(transparent, [0, 0, 0, 0]);
4119
4120 let mut half = [255u8, 128, 0, 128];
4122 premultiply_alpha(&mut half);
4123 assert_eq!(half, [128, 64, 0, 128]);
4124
4125 let mut max = [255u8, 255, 255, 255];
4127 premultiply_alpha(&mut max);
4128 assert_eq!(max, [255, 255, 255, 255]);
4129 }
4130
4131 #[test]
4132 fn au_from_px_saturates_at_limits_and_nan() {
4133 assert_eq!(Au::from_px(0.0).0, 0);
4134 assert_eq!(Au::from_px(-0.0).0, 0);
4135 assert_eq!(Au::from_px(1.0).0, AU_PER_PX);
4136 assert_eq!(Au::from_px(-1.0).0, -AU_PER_PX);
4137 assert_eq!(Au::from_px(f32::NAN).0, 0);
4139 assert_eq!(Au::from_px(f32::INFINITY).0, MAX_AU);
4141 assert_eq!(Au::from_px(f32::NEG_INFINITY).0, MIN_AU);
4142 assert_eq!(Au::from_px(f32::MAX).0, MAX_AU);
4143 assert_eq!(Au::from_px(f32::MIN).0, MIN_AU);
4144 for px in [-1.0e9_f32, -1.0, 0.5, 16.0, 1.0e9] {
4146 let au = Au::from_px(px).0;
4147 assert!((MIN_AU..=MAX_AU).contains(&au), "{px} -> {au} escaped the clamp");
4148 }
4149 }
4150
4151 #[test]
4152 fn au_px_round_trip_is_stable() {
4153 for px in [0.0_f32, 0.5, 1.0, 12.0, 16.0, 72.5, -3.25, 1000.0] {
4155 let back = Au::from_px(px).into_px();
4156 assert!(
4157 (back - px).abs() <= 1.0 / AU_PER_PX as f32,
4158 "{px} round-tripped to {back}"
4159 );
4160 }
4161 assert_eq!(Au::from_px(16.0).into_px(), 16.0);
4163 assert!(Au(MAX_AU).into_px().is_finite());
4165 assert!(Au(MIN_AU).into_px().is_finite());
4166 assert!(Au(i32::MIN).into_px().is_finite());
4167 assert!(Au(i32::MAX).into_px().is_finite());
4168 }
4169
4170 #[test]
4171 fn font_size_to_au_zero_negative_and_typical() {
4172 use azul_css::props::basic::PixelValue;
4173 let au = |px: isize| {
4174 font_size_to_au(StyleFontSize {
4175 inner: PixelValue::const_px(px),
4176 })
4177 .0
4178 };
4179 assert_eq!(au(0), 0);
4180 assert_eq!(au(16), 16 * AU_PER_PX);
4181 assert_eq!(au(-10), -10 * AU_PER_PX);
4182 assert!((MIN_AU..=MAX_AU).contains(&au(1_000_000)));
4184 assert!((MIN_AU..=MAX_AU).contains(&au(-1_000_000)));
4185 }
4186
4187 #[test]
4192 fn epoch_new_from_and_into_u32() {
4193 assert_eq!(Epoch::new().into_u32(), 0);
4194 assert_eq!(Epoch::default().into_u32(), 0);
4195 assert_eq!(Epoch::from(0).into_u32(), 0);
4196 assert_eq!(Epoch::from(1).into_u32(), 1);
4197 assert_eq!(Epoch::from(u32::MAX).into_u32(), u32::MAX);
4198 assert_eq!(Epoch::from(u32::MAX - 1).into_u32(), u32::MAX - 1);
4199 }
4200
4201 #[test]
4202 fn epoch_increment_wraps_at_max_minus_one_and_never_reaches_max() {
4203 let mut e = Epoch::new();
4204 e.increment();
4205 assert_eq!(e.into_u32(), 1);
4206
4207 let mut e = Epoch::from(u32::MAX - 1);
4209 e.increment();
4210 assert_eq!(e.into_u32(), 0, "MAX-1 must wrap to 0, never to u32::MAX");
4211
4212 let mut e = Epoch::from(u32::MAX);
4215 e.increment();
4216 assert_eq!(e.into_u32(), u32::MAX);
4217
4218 let mut e = Epoch::from(u32::MAX - 3);
4220 for _ in 0..8 {
4221 e.increment();
4222 assert_ne!(e.into_u32(), u32::MAX);
4223 }
4224 }
4225
4226 #[test]
4227 fn epoch_display_is_non_empty_for_edge_values() {
4228 assert_eq!(alloc::format!("{}", Epoch::new()), "0");
4229 assert_eq!(alloc::format!("{}", Epoch::from(42)), "42");
4230 assert_eq!(
4231 alloc::format!("{}", Epoch::from(u32::MAX)),
4232 alloc::format!("{}", u32::MAX)
4233 );
4234 assert!(!alloc::format!("{:?}", Epoch::default()).is_empty());
4235 }
4236
4237 #[test]
4238 fn id_namespace_display_and_debug_are_well_formed() {
4239 assert_eq!(alloc::format!("{}", IdNamespace(0)), "IdNamespace(0)");
4240 assert_eq!(
4241 alloc::format!("{}", IdNamespace(u32::MAX)),
4242 alloc::format!("IdNamespace({})", u32::MAX)
4243 );
4244 assert_eq!(
4246 alloc::format!("{:?}", IdNamespace(7)),
4247 alloc::format!("{}", IdNamespace(7))
4248 );
4249 }
4250
4251 #[test]
4256 fn unique_keys_are_strictly_increasing_and_keep_their_namespace() {
4257 let ns = IdNamespace(u32::MAX);
4258
4259 let a = ImageKey::unique(ns);
4260 let b = ImageKey::unique(ns);
4261 assert_eq!(a.namespace, ns);
4262 assert!(b.key > a.key, "ImageKey counter must strictly increase");
4263 assert_eq!(ImageKey::DUMMY.key, 0);
4265 assert_ne!(a, ImageKey::DUMMY);
4266
4267 let a = FontKey::unique(ns);
4268 let b = FontKey::unique(ns);
4269 assert_eq!(a.namespace, ns);
4270 assert!(b.key > a.key);
4271
4272 let a = FontInstanceKey::unique(IdNamespace(0));
4273 let b = FontInstanceKey::unique(IdNamespace(0));
4274 assert_eq!(a.namespace, IdNamespace(0));
4275 assert!(b.key > a.key);
4276 }
4277
4278 #[test]
4279 fn image_ref_id_counter_is_monotonic_and_never_zero() {
4280 let a = next_image_ref_id();
4282 let b = next_image_ref_id();
4283 assert!(a > 0 && b > a);
4284 }
4285
4286 #[test]
4287 fn image_ref_hash_conversions_are_lossless_and_agree() {
4288 let img = ImageRef::null_image(1, 1, RawImageFormat::RGBA8, Vec::new());
4289 let hash = img.get_hash();
4290 assert_eq!(hash, image_ref_get_hash(&img));
4291
4292 let key = image_ref_hash_to_image_key(hash, IdNamespace(9));
4293 assert_eq!(key.namespace, IdNamespace(9));
4294 assert_eq!(key.key, hash.inner, "the u64 id must survive verbatim");
4295
4296 let ext = image_ref_hash_to_external_image_id(hash);
4297 assert_eq!(ext.inner, hash.inner);
4298
4299 for inner in [0u64, 1, u64::MAX, u64::MAX - 1] {
4301 let h = ImageRefHash { inner };
4302 assert_eq!(image_ref_hash_to_image_key(h, IdNamespace(0)).key, inner);
4303 assert_eq!(image_ref_hash_to_external_image_id(h).inner, inner);
4304 }
4305 }
4306
4307 #[test]
4308 fn texture_external_image_id_is_deterministic_and_collision_free() {
4309 let id = |d: usize, n: usize| texture_external_image_id(DomId { inner: d }, NodeId::new(n));
4310
4311 assert_eq!(id(3, 7), id(3, 7));
4313 assert_eq!(id(0, 0).inner, 0);
4314 assert_eq!(id(1, 2).inner, (1u64 << 32) | 2);
4316 assert_ne!(id(0, 1), id(1, 0));
4318 assert_eq!(id(0, u32::MAX as usize).inner, u64::from(u32::MAX));
4320 assert_eq!(
4321 id(u32::MAX as usize, 0).inner,
4322 u64::from(u32::MAX) << 32
4323 );
4324 }
4325
4326 #[test]
4331 fn raw_image_data_typed_getters_only_match_their_own_variant() {
4332 let u8v = RawImageData::U8(vec![1u8, 2].into());
4333 let u16v = RawImageData::U16(vec![1u16, 2].into());
4334 let f32v = RawImageData::F32(vec![1.0f32, 2.0].into());
4335
4336 assert_eq!(u8v.get_u8_vec_ref().map(|v| v.len()), Some(2));
4337 assert!(u8v.get_u16_vec_ref().is_none());
4338 assert!(u8v.get_f32_vec_ref().is_none());
4339
4340 assert!(u16v.get_u8_vec_ref().is_none());
4341 assert_eq!(u16v.get_u16_vec_ref().map(|v| v.len()), Some(2));
4342 assert!(u16v.get_f32_vec_ref().is_none());
4343
4344 assert!(f32v.get_u8_vec_ref().is_none());
4345 assert!(f32v.get_u16_vec_ref().is_none());
4346 assert_eq!(f32v.get_f32_vec_ref().map(|v| v.len()), Some(2));
4347
4348 let empty = RawImageData::U8(U8Vec::from_vec(Vec::new()));
4350 assert_eq!(empty.get_u8_vec_ref().map(|v| v.len()), Some(0));
4351
4352 assert!(RawImageData::U8(vec![9u8].into()).get_u8_vec().is_some());
4354 assert!(RawImageData::U16(vec![9u16].into()).get_u8_vec().is_none());
4355 assert!(RawImageData::U16(vec![9u16].into()).get_u16_vec().is_some());
4356 assert!(RawImageData::F32(vec![9.0f32].into()).get_u16_vec().is_none());
4357 }
4358
4359 #[test]
4364 fn load_fns_reject_wrong_payload_type() {
4365 let u16_1px = || RawImageData::U16(vec![0u16; 4].into());
4368 let f32_1px = || RawImageData::F32(vec![0.0f32; 4].into());
4369 let u8_1px = || RawImageData::U8(vec![0u8; 4].into());
4370
4371 assert!(RawImage::load_r8(u16_1px(), 4).is_none());
4372 assert!(RawImage::load_rg8(f32_1px(), 2, true).is_none());
4373 assert!(RawImage::load_rgb8(u16_1px(), 1).is_none());
4374 assert!(RawImage::load_rgba8(f32_1px(), 1, true).is_none());
4375 assert!(RawImage::load_r16(u8_1px(), 4).is_none());
4376 assert!(RawImage::load_rg16(f32_1px(), 2).is_none());
4377 assert!(RawImage::load_rgb16(u8_1px(), 1).is_none());
4378 assert!(RawImage::load_rgba16(u8_1px(), 1, true).is_none());
4379 assert!(RawImage::load_bgr8(u16_1px(), 1).is_none());
4380 assert!(RawImage::load_bgra8(u16_1px(), 1, true).is_none());
4381 assert!(RawImage::load_rgbf32(u8_1px(), 1).is_none());
4382 assert!(RawImage::load_rgbaf32(u16_1px(), 1, true).is_none());
4383 }
4384
4385 #[test]
4386 fn load_fns_reject_every_wrong_length() {
4387 assert!(RawImage::load_r8(RawImageData::U8(vec![0u8; 3].into()), 4).is_none());
4389 assert!(RawImage::load_r8(RawImageData::U8(vec![0u8; 5].into()), 4).is_none());
4390 assert!(RawImage::load_rg8(RawImageData::U8(vec![0u8; 3].into()), 2, true).is_none());
4391 assert!(RawImage::load_rg8(RawImageData::U8(vec![0u8; 5].into()), 2, true).is_none());
4392 assert!(RawImage::load_rgb8(RawImageData::U8(vec![0u8; 5].into()), 2).is_none());
4393 assert!(RawImage::load_rgb8(RawImageData::U8(vec![0u8; 7].into()), 2).is_none());
4394 assert!(RawImage::load_rgba8(RawImageData::U8(vec![0u8; 7].into()), 2, true).is_none());
4395 assert!(RawImage::load_rgba8(RawImageData::U8(vec![0u8; 9].into()), 2, false).is_none());
4396 assert!(RawImage::load_r16(RawImageData::U16(vec![0u16; 3].into()), 4).is_none());
4397 assert!(RawImage::load_rg16(RawImageData::U16(vec![0u16; 3].into()), 2).is_none());
4398 assert!(RawImage::load_rgb16(RawImageData::U16(vec![0u16; 5].into()), 2).is_none());
4399 assert!(RawImage::load_rgba16(RawImageData::U16(vec![0u16; 7].into()), 2, true).is_none());
4400 assert!(RawImage::load_bgr8(RawImageData::U8(vec![0u8; 5].into()), 2).is_none());
4401 assert!(RawImage::load_bgra8(RawImageData::U8(vec![0u8; 7].into()), 2, false).is_none());
4402 assert!(RawImage::load_rgbf32(RawImageData::F32(vec![0.0f32; 5].into()), 2).is_none());
4403 assert!(
4404 RawImage::load_rgbaf32(RawImageData::F32(vec![0.0f32; 7].into()), 2, true).is_none()
4405 );
4406 }
4407
4408 #[test]
4409 fn load_fns_accept_zero_pixels() {
4410 let empty_u8 = || RawImageData::U8(U8Vec::from_vec(Vec::new()));
4412 let empty_u16 = || RawImageData::U16(U16Vec::from_vec(Vec::new()));
4413 let empty_f32 = || RawImageData::F32(F32Vec::from_vec(Vec::new()));
4414
4415 assert_eq!(RawImage::load_r8(empty_u8(), 0).map(|(b, o)| (b.len(), o)), Some((0, false)));
4416 assert_eq!(RawImage::load_rg8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4417 assert_eq!(RawImage::load_rgb8(empty_u8(), 0).map(|(b, _)| b.len()), Some(0));
4418 assert_eq!(RawImage::load_rgba8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4419 assert_eq!(RawImage::load_r16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4420 assert_eq!(RawImage::load_rg16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4421 assert_eq!(RawImage::load_rgb16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4422 assert_eq!(RawImage::load_rgba16(empty_u16(), 0, true).map(|(b, _)| b.len()), Some(0));
4423 assert_eq!(RawImage::load_bgr8(empty_u8(), 0).map(|(b, _)| b.len()), Some(0));
4424 assert_eq!(RawImage::load_bgra8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4425 assert_eq!(RawImage::load_rgbf32(empty_f32(), 0).map(|(b, _)| b.len()), Some(0));
4426 assert_eq!(RawImage::load_rgbaf32(empty_f32(), 0, true).map(|(b, _)| b.len()), Some(0));
4427 }
4428
4429 #[test]
4430 fn load_r8_passes_data_through_and_is_never_opaque() {
4431 let (bytes, is_opaque) =
4433 RawImage::load_r8(RawImageData::U8(vec![0u8, 128, 255, 1].into()), 4)
4434 .expect("exact length");
4435 assert_eq!(bytes.as_ref(), &[0, 128, 255, 1]);
4436 assert!(!is_opaque, "R8 is documented as never opaque");
4437 }
4438
4439 #[test]
4440 fn load_rgb8_and_bgr8_swizzle_to_bgra_opaque() {
4441 let (bytes, is_opaque) =
4443 RawImage::load_rgb8(RawImageData::U8(vec![1u8, 2, 3].into()), 1).expect("1 px");
4444 assert_eq!(bytes.as_ref(), &[3, 2, 1, 255]);
4445 assert!(is_opaque);
4446
4447 let (bytes, is_opaque) =
4449 RawImage::load_bgr8(RawImageData::U8(vec![1u8, 2, 3].into()), 1).expect("1 px");
4450 assert_eq!(bytes.as_ref(), &[1, 2, 3, 255]);
4451 assert!(is_opaque);
4452 }
4453
4454 #[test]
4455 fn load_rgba8_swizzles_and_detects_transparency() {
4456 let (bytes, is_opaque) =
4458 RawImage::load_rgba8(RawImageData::U8(vec![10u8, 20, 30, 255].into()), 1, true)
4459 .expect("1 px");
4460 assert_eq!(bytes.as_ref(), &[30, 20, 10, 255]);
4461 assert!(is_opaque);
4462
4463 let (_, is_opaque) =
4465 RawImage::load_rgba8(RawImageData::U8(vec![0u8, 0, 0, 254].into()), 1, true)
4466 .expect("1 px");
4467 assert!(!is_opaque);
4468
4469 let (bytes, is_opaque) =
4471 RawImage::load_rgba8(RawImageData::U8(vec![10u8, 20, 30, 128].into()), 1, false)
4472 .expect("1 px");
4473 assert_eq!(bytes.as_ref(), &[15, 10, 5, 128]);
4474 assert!(!is_opaque);
4475
4476 let (bytes, _) =
4478 RawImage::load_rgba8(RawImageData::U8(vec![255u8, 255, 255, 0].into()), 1, false)
4479 .expect("1 px");
4480 assert_eq!(bytes.as_ref(), &[0, 0, 0, 0]);
4481 }
4482
4483 #[test]
4484 fn load_rg8_expands_grey_to_bgra() {
4485 let (bytes, is_opaque) =
4487 RawImage::load_rg8(RawImageData::U8(vec![100u8, 255].into()), 1, true).expect("1 px");
4488 assert_eq!(bytes.as_ref(), &[100, 100, 100, 255]);
4489 assert!(is_opaque);
4490
4491 let (bytes, is_opaque) =
4492 RawImage::load_rg8(RawImageData::U8(vec![100u8, 128].into()), 1, false).expect("1 px");
4493 assert_eq!(bytes.as_ref(), &[50, 50, 50, 128]);
4494 assert!(!is_opaque);
4495 }
4496
4497 #[test]
4498 fn load_16_bit_formats_normalize_to_8_bit() {
4499 let (bytes, is_opaque) =
4501 RawImage::load_r16(RawImageData::U16(vec![u16::MAX].into()), 1).expect("1 px");
4502 assert_eq!(bytes.as_ref(), &[255, 255, 255, 255]);
4503 assert!(is_opaque);
4504
4505 let (bytes, is_opaque) =
4506 RawImage::load_rg16(RawImageData::U16(vec![0u16, u16::MAX].into()), 1).expect("1 px");
4507 assert_eq!(bytes.as_ref(), &[0, 0, 0, 255]);
4508 assert!(is_opaque);
4509
4510 let (bytes, _) = RawImage::load_rgb16(
4512 RawImageData::U16(vec![u16::MAX, 0, 0].into()),
4513 1,
4514 )
4515 .expect("1 px");
4516 assert_eq!(bytes.as_ref(), &[0, 0, 255, 255]);
4517
4518 let (bytes, is_opaque) = RawImage::load_rgba16(
4520 RawImageData::U16(vec![u16::MAX, u16::MAX, u16::MAX, 0].into()),
4521 1,
4522 false,
4523 )
4524 .expect("1 px");
4525 assert_eq!(bytes.as_ref(), &[0, 0, 0, 0]);
4526 assert!(!is_opaque);
4527 }
4528
4529 #[test]
4530 fn load_f32_formats_saturate_on_out_of_range_nan_and_inf() {
4531 let (bytes, is_opaque) = RawImage::load_rgbf32(
4534 RawImageData::F32(vec![2.0f32, -1.0, f32::NAN].into()),
4535 1,
4536 )
4537 .expect("1 px");
4538 assert_eq!(bytes.as_ref(), &[0, 0, 255, 255], "b=NaN->0, g=-1->0, r=2.0->255");
4539 assert!(is_opaque);
4540
4541 let (bytes, is_opaque) = RawImage::load_rgbaf32(
4542 RawImageData::F32(vec![f32::INFINITY, f32::NEG_INFINITY, 0.5, 1.0].into()),
4543 1,
4544 true,
4545 )
4546 .expect("1 px");
4547 assert_eq!(bytes.as_ref(), &[127, 0, 255, 255]);
4548 assert!(is_opaque);
4549
4550 let (_, is_opaque) = RawImage::load_rgbaf32(
4552 RawImageData::F32(vec![1.0f32, 1.0, 1.0, f32::NAN].into()),
4553 1,
4554 true,
4555 )
4556 .expect("1 px");
4557 assert!(!is_opaque);
4558 }
4559
4560 #[test]
4565 fn raw_image_null_image_encodes_to_an_empty_bgra8_descriptor() {
4566 let null = RawImage::null_image();
4567 assert_eq!(null.width, 0);
4568 assert_eq!(null.height, 0);
4569 assert_eq!(null.data_format, RawImageFormat::BGRA8);
4570 assert!(null.premultiplied_alpha);
4571
4572 let (data, descriptor) = null
4573 .into_loaded_image_source()
4574 .expect("a 0x0 image is still a valid (empty) source");
4575 assert_eq!(descriptor.width, 0);
4576 assert_eq!(descriptor.height, 0);
4577 assert_eq!(descriptor.format, RawImageFormat::BGRA8);
4578 assert_eq!(descriptor.offset, 0);
4579 match data {
4580 ImageData::Raw(bytes) => assert!(bytes.is_empty()),
4581 ImageData::External(_) => panic!("a RawImage must never encode to External"),
4582 }
4583 }
4584
4585 #[test]
4586 fn raw_image_allocate_mask_zero_and_negative_sizes() {
4587 let mask = RawImage::allocate_mask(LayoutSize::zero());
4588 assert_eq!(mask.data_format, RawImageFormat::R8);
4589 assert_eq!(mask.width, 0);
4590 assert_eq!(mask.height, 0);
4591 assert_eq!(mask.pixels.get_u8_vec_ref().map(|v| v.len()), Some(0));
4592
4593 let mask = RawImage::allocate_mask(LayoutSize::new(4, 4));
4594 assert_eq!(mask.pixels.get_u8_vec_ref().map(|v| v.len()), Some(16));
4595 assert!(mask
4596 .pixels
4597 .get_u8_vec_ref()
4598 .expect("u8")
4599 .as_ref()
4600 .iter()
4601 .all(|b| *b == 0));
4602
4603 let mask = RawImage::allocate_mask(LayoutSize::new(-4, 4));
4608 assert_eq!(
4609 mask.pixels.get_u8_vec_ref().map(|v| v.len()),
4610 Some(0),
4611 "a negative extent must never allocate"
4612 );
4613 assert!(mask.width > 1_000_000, "negative width wraps via `as usize`");
4614 }
4615
4616 #[test]
4617 fn raw_image_mask_round_trips_as_r8() {
4618 let mask = RawImage::allocate_mask(LayoutSize::new(2, 2));
4621 let (data, descriptor) = mask.into_loaded_image_source().expect("consistent mask");
4622 assert_eq!(descriptor.format, RawImageFormat::R8);
4623 assert_eq!((descriptor.width, descriptor.height), (2, 2));
4624 assert!(!descriptor.flags.is_opaque, "R8 is never opaque");
4625 match data {
4626 ImageData::Raw(bytes) => assert_eq!(bytes.len(), 4),
4627 ImageData::External(_) => panic!("expected raw bytes"),
4628 }
4629 }
4630
4631 #[test]
4632 fn raw_image_rgba8_encode_decode_round_trip() {
4633 let raw = RawImage {
4636 pixels: RawImageData::U8(vec![10u8, 20, 30, 255].into()),
4637 width: 1,
4638 height: 1,
4639 premultiplied_alpha: true,
4640 data_format: RawImageFormat::RGBA8,
4641 tag: Vec::new().into(),
4642 };
4643 let img = ImageRef::new_rawimage(raw).expect("1x1 RGBA8 with 4 bytes is valid");
4644
4645 assert!(img.is_raw_image());
4646 assert!(!img.is_null_image());
4647 assert!(!img.is_gl_texture());
4648 assert!(!img.is_callback());
4649 assert_eq!(img.get_size(), LogicalSize::new(1.0, 1.0));
4650 assert_eq!(img.get_bytes(), Some(&[30u8, 20, 10, 255][..]));
4651 assert!(!img.get_bytes_ptr().is_null());
4652
4653 let decoded = img.get_rawimage().expect("raw image round-trips");
4654 assert_eq!(decoded.width, 1);
4655 assert_eq!(decoded.height, 1);
4656 assert_eq!(decoded.data_format, RawImageFormat::BGRA8);
4657 assert!(decoded.premultiplied_alpha);
4658 assert_eq!(
4659 decoded.pixels.get_u8_vec_ref().map(|v| v.as_ref().to_vec()),
4660 Some(vec![30, 20, 10, 255])
4661 );
4662 }
4663
4664 #[test]
4665 fn image_ref_new_rawimage_rejects_dimension_mismatch() {
4666 let too_small = RawImage {
4668 pixels: RawImageData::U8(vec![0u8; 4].into()),
4669 width: 2,
4670 height: 2,
4671 premultiplied_alpha: true,
4672 data_format: RawImageFormat::RGBA8,
4673 tag: Vec::new().into(),
4674 };
4675 assert!(ImageRef::new_rawimage(too_small).is_none());
4676
4677 let too_big = RawImage {
4679 pixels: RawImageData::U8(vec![0u8; 64].into()),
4680 width: 2,
4681 height: 2,
4682 premultiplied_alpha: true,
4683 data_format: RawImageFormat::RGBA8,
4684 tag: Vec::new().into(),
4685 };
4686 assert!(ImageRef::new_rawimage(too_big).is_none());
4687
4688 let wrong_type = RawImage {
4690 pixels: RawImageData::U16(vec![0u16; 16].into()),
4691 width: 2,
4692 height: 2,
4693 premultiplied_alpha: true,
4694 data_format: RawImageFormat::RGBA8,
4695 tag: Vec::new().into(),
4696 };
4697 assert!(ImageRef::new_rawimage(wrong_type).is_none());
4698 }
4699
4700 #[test]
4705 fn image_ref_null_image_predicates_and_accessors() {
4706 let img = ImageRef::null_image(0, 0, RawImageFormat::BGRA8, Vec::new());
4707 assert!(img.is_null_image());
4708 assert!(!img.is_raw_image());
4709 assert!(!img.is_gl_texture());
4710 assert!(!img.is_callback());
4711 assert_eq!(img.get_size(), LogicalSize::new(0.0, 0.0));
4712 assert!(img.get_bytes().is_none());
4713 assert!(img.get_rawimage().is_none());
4714 assert!(img.get_bytes_ptr().is_null());
4715 assert!(img.get_image_callback().is_none());
4716 assert!(matches!(img.get_data(), DecodedImage::NullImage { .. }));
4717 }
4718
4719 #[test]
4720 fn image_ref_null_image_at_usize_max_reports_a_finite_size() {
4721 let img = ImageRef::null_image(usize::MAX, usize::MAX, RawImageFormat::R8, Vec::new());
4723 let size = img.get_size();
4724 assert!(size.width.is_finite() && size.height.is_finite());
4725 assert!(size.width > 0.0 && size.height > 0.0);
4726 assert!(img.is_null_image());
4727
4728 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, vec![9u8; 10_000]);
4730 match img.get_data() {
4731 DecodedImage::NullImage { tag, .. } => assert_eq!(tag.len(), 10_000),
4732 _ => panic!("expected NullImage"),
4733 }
4734 }
4735
4736 #[test]
4737 fn image_ref_hash_identity_rules() {
4738 let a = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4739 let b = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4740 assert_ne!(a.get_hash(), b.get_hash());
4742 assert_ne!(a, b);
4743
4744 let a2 = a.clone();
4746 assert_eq!(a.get_hash(), a2.get_hash());
4747 assert_eq!(a, a2);
4748
4749 let deep = a.deep_copy();
4751 assert_ne!(a.get_hash(), deep.get_hash());
4752 assert!(deep.is_null_image());
4753 assert_eq!(deep.get_size(), a.get_size());
4754 }
4755
4756 #[test]
4757 fn image_ref_callback_accessors() {
4758 let mut img = ImageRef::callback(0usize, RefAny::new(123u32));
4761 assert!(img.is_callback());
4762 assert!(!img.is_null_image());
4763 assert!(!img.is_raw_image());
4764 assert_eq!(img.get_size(), LogicalSize::new(0.0, 0.0));
4766 assert!(img.get_bytes().is_none());
4767 assert!(img.get_bytes_ptr().is_null());
4768 assert!(img.get_rawimage().is_none());
4769
4770 assert!(img.get_image_callback().is_some());
4772 assert!(img.get_image_callback_mut().is_some());
4773
4774 let clone = img.clone();
4777 assert!(img.get_image_callback().is_none());
4778 assert!(img.get_image_callback_mut().is_none());
4779 drop(clone);
4780 assert!(img.get_image_callback().is_some());
4781 }
4782
4783 #[test]
4784 fn image_ref_deep_copy_of_a_callback_keeps_it_a_callback() {
4785 let img = ImageRef::callback(0usize, RefAny::new(1u8));
4786 let deep = img.deep_copy();
4787 assert!(deep.is_callback());
4788 assert_ne!(img.get_hash(), deep.get_hash());
4789 }
4790
4791 #[test]
4792 fn image_ref_into_inner_only_when_sole_owner() {
4793 let img = ImageRef::null_image(2, 2, RawImageFormat::RGBA8, vec![1, 2, 3]);
4794 let clone = img.clone();
4795 assert!(clone.into_inner().is_none(), "shared -> must refuse");
4796
4797 let inner = img.into_inner().expect("sole owner -> takes ownership");
4798 match inner {
4799 DecodedImage::NullImage {
4800 width,
4801 height,
4802 format,
4803 tag,
4804 } => {
4805 assert_eq!((width, height), (2, 2));
4806 assert_eq!(format, RawImageFormat::RGBA8);
4807 assert_eq!(tag, vec![1, 2, 3]);
4808 }
4809 _ => panic!("expected NullImage"),
4810 }
4811 }
4812
4813 #[test]
4818 fn image_cache_add_get_delete_round_trip() {
4819 let mut cache = ImageCache::new();
4820 let key = AzString::from_const_str("my_image");
4821 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4822 let hash = img.get_hash();
4823
4824 assert!(cache.get_css_image_id(&key).is_none());
4825 cache.add_css_image_id(key.clone(), img);
4826 assert_eq!(cache.get_css_image_id(&key).map(ImageRef::get_hash), Some(hash));
4827
4828 let img2 = ImageRef::null_image(2, 2, RawImageFormat::R8, Vec::new());
4830 let hash2 = img2.get_hash();
4831 cache.add_css_image_id(key.clone(), img2);
4832 assert_eq!(cache.image_id_map.len(), 1);
4833 assert_eq!(cache.get_css_image_id(&key).map(ImageRef::get_hash), Some(hash2));
4834
4835 cache.delete_css_image_id(&key);
4836 assert!(cache.get_css_image_id(&key).is_none());
4837 assert!(cache.image_id_map.is_empty());
4838 cache.delete_css_image_id(&key);
4840 cache.delete_css_image_id(&AzString::from_const_str("never-existed"));
4841 }
4842
4843 #[test]
4844 fn image_cache_handles_empty_and_unicode_keys() {
4845 let mut cache = ImageCache::new();
4846 let empty = AzString::from_const_str("");
4847 let unicode = AzString::from(String::from("\u{1F600}\u{0301}"));
4848 let long = AzString::from("k".repeat(100_000));
4849
4850 cache.add_css_image_id(
4851 empty.clone(),
4852 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4853 );
4854 cache.add_css_image_id(
4855 unicode.clone(),
4856 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4857 );
4858 cache.add_css_image_id(
4859 long.clone(),
4860 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4861 );
4862
4863 assert_eq!(cache.image_id_map.len(), 3);
4864 assert!(cache.get_css_image_id(&empty).is_some());
4865 assert!(cache.get_css_image_id(&unicode).is_some());
4866 assert!(cache.get_css_image_id(&long).is_some());
4867 assert!(cache
4869 .get_css_image_id(&AzString::from_const_str("\u{1F600}"))
4870 .is_none());
4871 }
4872
4873 #[test]
4878 fn renderer_resources_lookups_on_an_empty_registry_are_none() {
4879 let rr = RendererResources::default();
4880 let ns = IdNamespace(1);
4881 assert!(rr
4882 .get_renderable_font_data(&FontInstanceKey::unique(ns))
4883 .is_none());
4884 let families = StyleFontFamiliesHash::new(&[]);
4885 assert!(rr
4886 .get_font_instance_key(&families, Au(0), DpiScaleFactor::new(1.0))
4887 .is_none());
4888 assert!(rr
4889 .get_font_instance_key(&families, Au(MAX_AU), DpiScaleFactor::new(f32::NAN))
4890 .is_none());
4891 assert!(rr.get_image(&ImageRefHash { inner: 0 }).is_none());
4892 assert!(rr.get_font_key(&StyleFontFamilyHash::new(&StyleFontFamily::System(
4893 AzString::from_const_str("Arial")
4894 ))).is_none());
4895 }
4896
4897 #[test]
4898 fn renderer_resources_gc_helper_is_a_noop_on_empty_maps() {
4899 let mut rr = RendererResources::default();
4901 rr.remove_font_families_with_zero_references();
4902 assert!(rr.font_id_map.is_empty());
4903 assert!(rr.font_families_map.is_empty());
4904
4905 let family = StyleFontFamily::System(AzString::from_const_str("Arial"));
4908 let family_hash = StyleFontFamilyHash::new(&family);
4909 let families_hash = StyleFontFamiliesHash::new(core::slice::from_ref(&family));
4910 rr.font_id_map.insert(family_hash, FontKey::unique(IdNamespace(1)));
4911 rr.font_families_map.insert(families_hash, family_hash);
4912 rr.remove_font_families_with_zero_references();
4913 assert!(rr.font_id_map.is_empty(), "dangling font key must be pruned");
4914 assert!(rr.font_families_map.is_empty());
4915 }
4916
4917 #[test]
4918 fn get_font_instance_key_for_text_is_none_on_empty_resources_for_all_sane_sizes() {
4919 let rr = RendererResources::default();
4922 let cache = CssPropertyCache::default();
4923 let node = NodeData::default();
4924 let node_id = NodeId::new(0);
4925 let state = StyledNodeState::default();
4926
4927 for size in [0.0_f32, -0.0, 1.0, -12.0, f32::NAN, 1.0e6, -1.0e6] {
4928 for dpi in [1.0_f32, 0.0, -1.0, f32::NAN, f32::INFINITY] {
4929 assert!(
4930 rr.get_font_instance_key_for_text(size, &cache, &node, &node_id, &state, dpi)
4931 .is_none(),
4932 "size={size} dpi={dpi} must miss cleanly"
4933 );
4934 }
4935 }
4936 }
4937
4938 #[test]
4939 #[ignore = "BUG: font_size_px = inf/f32::MAX overflows `isize * 1000` inside \
4940 PixelValue::const_px -> panics instead of saturating"]
4941 fn bug_get_font_instance_key_for_text_overflow_panics_on_infinite_font_size() {
4942 let rr = RendererResources::default();
4947 let cache = CssPropertyCache::default();
4948 let node = NodeData::default();
4949 let node_id = NodeId::new(0);
4950 let state = StyledNodeState::default();
4951 assert!(rr
4952 .get_font_instance_key_for_text(
4953 f32::INFINITY,
4954 &cache,
4955 &node,
4956 &node_id,
4957 &state,
4958 1.0
4959 )
4960 .is_none());
4961 }
4962
4963 #[test]
4968 fn font_ref_get_hash_is_stable_per_font_and_distinct_across_fonts() {
4969 let a = dummy_font_ref();
4970 let b = dummy_font_ref();
4971 assert_eq!(font_ref_get_hash(&a), font_ref_get_hash(&a));
4972 assert_eq!(font_ref_get_hash(&a), font_ref_get_hash(&a.clone()));
4973 assert_ne!(
4974 font_ref_get_hash(&a),
4975 font_ref_get_hash(&b),
4976 "two distinct FontRefs must not share a hash"
4977 );
4978 }
4979
4980 #[test]
4981 fn build_add_font_resource_updates_on_empty_input_is_empty() {
4982 let mut rr = RendererResources::default();
4983 let fonts = OrderedMap::new();
4984 let updates = build_add_font_resource_updates(
4985 &mut rr,
4986 DpiScaleFactor::new(1.0),
4987 &FcFontCache::default(),
4988 IdNamespace(1),
4989 &fonts,
4990 load_font_none,
4991 parse_font_none,
4992 );
4993 assert!(updates.is_empty());
4994 assert!(rr.font_id_map.is_empty());
4995 }
4996
4997 #[test]
4998 fn build_add_font_resource_updates_skips_unloadable_fonts() {
4999 let mut rr = RendererResources::default();
5002 let mut fonts = OrderedMap::new();
5003 let mut sizes = FastBTreeSet::new();
5004 sizes.insert(Au::from_px(16.0));
5005 fonts.insert(
5006 ImmediateFontId::Unresolved(StyleFontFamilyVec::from_vec(vec![
5007 StyleFontFamily::System(AzString::from_const_str("DoesNotExist")),
5008 ])),
5009 sizes,
5010 );
5011
5012 let updates = build_add_font_resource_updates(
5013 &mut rr,
5014 DpiScaleFactor::new(1.0),
5015 &FcFontCache::default(),
5016 IdNamespace(1),
5017 &fonts,
5018 load_font_none,
5019 parse_font_none,
5020 );
5021 assert!(updates.is_empty(), "an unloadable font must add no resources");
5022 assert!(rr.font_id_map.is_empty());
5023 assert!(rr.font_families_map.is_empty());
5024 }
5025
5026 #[test]
5027 fn build_add_font_resource_updates_registers_a_font_and_deduplicates_sizes() {
5028 let mut rr = RendererResources::default();
5031 let font = dummy_font_ref();
5032 let family = StyleFontFamily::Ref(font.clone());
5033 let dpi = DpiScaleFactor::new(1.0);
5034
5035 let mut sizes = FastBTreeSet::new();
5036 sizes.insert(Au::from_px(16.0));
5037 sizes.insert(Au::from_px(24.0));
5038 sizes.insert(Au::from_px(16.0)); assert_eq!(sizes.len(), 2);
5040
5041 let mut fonts = OrderedMap::new();
5042 fonts.insert(
5043 ImmediateFontId::Unresolved(StyleFontFamilyVec::from_vec(vec![family.clone()])),
5044 sizes,
5045 );
5046
5047 let updates = build_add_font_resource_updates(
5048 &mut rr,
5049 dpi,
5050 &FcFontCache::default(),
5051 IdNamespace(1),
5052 &fonts,
5053 load_font_none,
5054 parse_font_none,
5055 );
5056 assert_eq!(updates.len(), 3);
5058 assert_eq!(
5059 updates
5060 .iter()
5061 .filter(|(_, m)| matches!(m, AddFontMsg::Font(..)))
5062 .count(),
5063 1
5064 );
5065 assert_eq!(
5066 updates
5067 .iter()
5068 .filter(|(_, m)| matches!(m, AddFontMsg::Instance(..)))
5069 .count(),
5070 2
5071 );
5072 assert_eq!(rr.font_id_map.len(), 1);
5073 assert_eq!(rr.font_families_map.len(), 1);
5074
5075 let mut all_updates = Vec::new();
5077 add_resources(&mut rr, &mut all_updates, updates, Vec::new());
5078 assert_eq!(all_updates.len(), 3);
5079
5080 let families_hash = StyleFontFamiliesHash::new(core::slice::from_ref(&family));
5081 assert!(rr
5082 .get_font_instance_key(&families_hash, Au::from_px(16.0), dpi)
5083 .is_some());
5084 assert!(rr
5085 .get_font_instance_key(&families_hash, Au::from_px(24.0), dpi)
5086 .is_some());
5087 assert!(rr
5089 .get_font_instance_key(&families_hash, Au::from_px(99.0), dpi)
5090 .is_none());
5091 assert!(rr
5092 .get_font_instance_key(&families_hash, Au::from_px(16.0), DpiScaleFactor::new(2.0))
5093 .is_none());
5094
5095 let key = rr
5097 .get_font_instance_key(&families_hash, Au::from_px(16.0), dpi)
5098 .expect("registered");
5099 let (font_ref, au, got_dpi) = rr
5100 .get_renderable_font_data(&key)
5101 .expect("registered instance must be renderable");
5102 assert_eq!(font_ref.get_hash(), font.get_hash());
5103 assert_eq!(au, Au::from_px(16.0));
5104 assert_eq!(got_dpi, dpi);
5105
5106 let again = build_add_font_resource_updates(
5108 &mut rr,
5109 dpi,
5110 &FcFontCache::default(),
5111 IdNamespace(1),
5112 &fonts,
5113 load_font_none,
5114 parse_font_none,
5115 );
5116 assert!(again.is_empty(), "already-registered fonts must not be re-added");
5117 }
5118
5119 #[test]
5120 fn add_font_msg_into_resource_update_preserves_keys() {
5121 let font = dummy_font_ref();
5122 let key = FontKey::unique(IdNamespace(3));
5123 let family_hash = StyleFontFamilyHash::new(&StyleFontFamily::Ref(font.clone()));
5124 let msg = AddFontMsg::Font(key, family_hash, font.clone());
5125 match msg.into_resource_update() {
5126 ResourceUpdate::AddFont(add) => {
5127 assert_eq!(add.key, key);
5128 assert_eq!(add.font.get_hash(), font.get_hash());
5129 }
5130 other => panic!("expected AddFont, got {other:?}"),
5131 }
5132 }
5133
5134 #[test]
5135 fn delete_font_msg_into_resource_update_preserves_keys() {
5136 let fk = FontKey::unique(IdNamespace(1));
5137 match DeleteFontMsg::Font(fk).into_resource_update() {
5138 ResourceUpdate::DeleteFont(k) => assert_eq!(k, fk),
5139 other => panic!("expected DeleteFont, got {other:?}"),
5140 }
5141 let fik = FontInstanceKey::unique(IdNamespace(1));
5142 let size = (Au::from_px(16.0), DpiScaleFactor::new(1.0));
5143 match DeleteFontMsg::Instance(fik, size).into_resource_update() {
5144 ResourceUpdate::DeleteFontInstance(k) => assert_eq!(k, fik),
5145 other => panic!("expected DeleteFontInstance, got {other:?}"),
5146 }
5147 }
5148
5149 #[test]
5150 fn add_image_msg_into_resource_update_preserves_the_key_and_descriptor() {
5151 let key = ImageKey::unique(IdNamespace(2));
5152 let descriptor = ImageDescriptor {
5153 format: RawImageFormat::BGRA8,
5154 width: 3,
5155 height: 5,
5156 stride: None.into(),
5157 offset: 0,
5158 flags: ImageDescriptorFlags {
5159 is_opaque: false,
5160 allow_mipmaps: true,
5161 },
5162 };
5163 let msg = AddImageMsg(AddImage {
5164 key,
5165 descriptor,
5166 data: ImageData::Raw(SharedRawImageData::new(vec![0u8; 60].into())),
5167 tiling: None,
5168 });
5169 match msg.into_resource_update() {
5170 ResourceUpdate::AddImage(add) => {
5171 assert_eq!(add.key, key);
5172 assert_eq!(add.descriptor, descriptor);
5173 assert!(add.tiling.is_none());
5174 }
5175 other => panic!("expected AddImage, got {other:?}"),
5176 }
5177 }
5178
5179 #[test]
5180 fn build_add_image_resource_updates_skips_null_and_callback_images() {
5181 let rr = RendererResources::default();
5183 let mut images = FastBTreeSet::new();
5184 images.insert(ImageRef::null_image(4, 4, RawImageFormat::RGBA8, Vec::new()));
5185 images.insert(ImageRef::callback(0usize, RefAny::new(0u8)));
5186
5187 let updates = build_add_image_resource_updates(
5188 &rr,
5189 IdNamespace(1),
5190 Epoch::new(),
5191 &test_document_id(),
5192 &images,
5193 store_gl_texture_noop,
5194 );
5195 assert!(updates.is_empty());
5196
5197 let empty = FastBTreeSet::new();
5199 assert!(build_add_image_resource_updates(
5200 &rr,
5201 IdNamespace(1),
5202 Epoch::new(),
5203 &test_document_id(),
5204 &empty,
5205 store_gl_texture_noop,
5206 )
5207 .is_empty());
5208 }
5209
5210 #[test]
5211 fn build_add_image_resource_updates_then_add_resources_round_trip() {
5212 let mut rr = RendererResources::default();
5213 let img = ImageRef::new_rawimage(rgba8_image(2, 2)).expect("valid 2x2");
5214 let hash = img.get_hash();
5215 let ns = IdNamespace(11);
5216
5217 let mut images = FastBTreeSet::new();
5218 images.insert(img.clone());
5219
5220 let updates = build_add_image_resource_updates(
5221 &rr,
5222 ns,
5223 Epoch::new(),
5224 &test_document_id(),
5225 &images,
5226 store_gl_texture_noop,
5227 );
5228 assert_eq!(updates.len(), 1);
5229 assert_eq!(updates[0].0, hash);
5230 assert_eq!(updates[0].1 .0.key, image_ref_hash_to_image_key(hash, ns));
5232 assert_eq!(updates[0].1 .0.descriptor.width, 2);
5233 assert_eq!(updates[0].1 .0.descriptor.height, 2);
5234
5235 let key = updates[0].1 .0.key;
5236 let mut all_updates = Vec::new();
5237 add_resources(&mut rr, &mut all_updates, Vec::new(), updates);
5238 assert_eq!(all_updates.len(), 1);
5239 assert!(matches!(all_updates[0], ResourceUpdate::AddImage(_)));
5240
5241 assert_eq!(rr.get_image(&hash).map(|r| r.key), Some(key));
5243 assert_eq!(rr.image_key_map.get(&key), Some(&hash));
5244
5245 let again = build_add_image_resource_updates(
5247 &rr,
5248 ns,
5249 Epoch::new(),
5250 &test_document_id(),
5251 &images,
5252 store_gl_texture_noop,
5253 );
5254 assert!(again.is_empty());
5255
5256 let new_descriptor = ImageDescriptor {
5258 format: RawImageFormat::BGRA8,
5259 width: 8,
5260 height: 8,
5261 stride: None.into(),
5262 offset: 0,
5263 flags: ImageDescriptorFlags {
5264 is_opaque: true,
5265 allow_mipmaps: true,
5266 },
5267 };
5268 rr.update_image(&hash, new_descriptor);
5269 assert_eq!(rr.get_image(&hash).map(|r| r.descriptor.width), Some(8));
5270 assert_eq!(rr.get_image(&hash).map(|r| r.key), Some(key));
5271 rr.update_image(&ImageRefHash { inner: u64::MAX }, new_descriptor);
5273 }
5274
5275 #[test]
5276 fn add_resources_with_empty_input_changes_nothing() {
5277 let mut rr = RendererResources::default();
5278 let mut updates = Vec::new();
5279 add_resources(&mut rr, &mut updates, Vec::new(), Vec::new());
5280 assert!(updates.is_empty());
5281 assert!(rr.currently_registered_images.is_empty());
5282 assert!(rr.currently_registered_fonts.is_empty());
5283 assert!(rr.image_key_map.is_empty());
5284 }
5285
5286 #[test]
5291 fn paint_dot_composites_at_the_center_and_leaves_far_pixels_alone() {
5292 let mut img = rgba8_image(4, 4);
5293 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5294 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5295
5296 let idx = (1 * 4 + 1) * 4;
5298 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255], "center pixel must be opaque red");
5299 assert_eq!(&px[0..4], &[0, 0, 0, 0], "pixels beyond the radius stay untouched");
5301 }
5302
5303 #[test]
5304 fn paint_dot_honours_bgra_channel_order() {
5305 let mut img = rgba8_image(4, 4);
5306 img.data_format = RawImageFormat::BGRA8;
5307 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5308 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5309 let idx = (1 * 4 + 1) * 4;
5310 assert_eq!(&px[idx..idx + 4], &[0, 0, 255, 255]);
5312 }
5313
5314 #[test]
5315 fn paint_dot_rejects_degenerate_radii_and_sizes() {
5316 let untouched = |img: &RawImage| {
5317 img.pixels
5318 .get_u8_vec_ref()
5319 .expect("u8")
5320 .as_ref()
5321 .iter()
5322 .all(|b| *b == 0)
5323 };
5324
5325 for r in [0.0_f32, -1.0, -0.0, f32::NAN, f32::NEG_INFINITY] {
5327 let mut img = rgba8_image(4, 4);
5328 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), r));
5329 assert!(untouched(&img), "radius {r} must not paint");
5330 }
5331
5332 let mut img = rgba8_image(0, 0);
5334 img.paint_dot(0.0, 0.0, Brush::new(opaque_red(), 4.0));
5335 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(0));
5336
5337 for format in [
5339 RawImageFormat::R8,
5340 RawImageFormat::RGB8,
5341 RawImageFormat::RGBA16,
5342 RawImageFormat::RGBAF32,
5343 ] {
5344 let mut img = rgba8_image(4, 4);
5345 img.data_format = format;
5346 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5347 assert!(untouched(&img), "format {format:?} must not be painted");
5348 }
5349 }
5350
5351 #[test]
5352 fn paint_dot_with_nan_and_infinite_coordinates_is_a_safe_noop() {
5353 for (cx, cy) in [
5356 (f32::NAN, 2.0_f32),
5357 (2.0, f32::NAN),
5358 (f32::NAN, f32::NAN),
5359 (f32::INFINITY, 2.0),
5360 (f32::NEG_INFINITY, 2.0),
5361 (2.0, f32::INFINITY),
5362 (1.0e30, 1.0e30),
5363 (-1.0e30, -1.0e30),
5364 ] {
5365 let mut img = rgba8_image(4, 4);
5366 img.paint_dot(cx, cy, Brush::new(opaque_red(), 2.0));
5367 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5368 assert!(
5369 px.iter().all(|b| *b == 0),
5370 "({cx}, {cy}) must not paint anything"
5371 );
5372 }
5373 }
5374
5375 #[test]
5376 fn paint_dot_alpha_saturates_and_never_overflows() {
5377 let mut img = rgba8_image(4, 4);
5379 let brush = Brush::new(opaque_red(), 2.0);
5380 for _ in 0..50 {
5381 img.paint_dot(2.0, 2.0, brush);
5382 }
5383 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5384 let idx = (1 * 4 + 1) * 4;
5385 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255]);
5386
5387 let mut img = rgba8_image(4, 4);
5392 let mut nan_brush = Brush::new(opaque_red(), 2.0);
5393 nan_brush.hardness = f32::NAN;
5394 img.paint_dot(2.0, 2.0, nan_brush);
5395 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(64));
5398 }
5399
5400 #[test]
5401 fn paint_dot_zero_flow_and_transparent_color_do_not_paint() {
5402 let mut img = rgba8_image(4, 4);
5403 let mut brush = Brush::new(opaque_red(), 2.0);
5404 brush.flow = 0.0;
5405 img.paint_dot(2.0, 2.0, brush);
5406 assert!(img
5407 .pixels
5408 .get_u8_vec_ref()
5409 .expect("u8")
5410 .as_ref()
5411 .iter()
5412 .all(|b| *b == 0));
5413
5414 let mut img = rgba8_image(4, 4);
5415 let transparent = ColorU {
5416 r: 255,
5417 g: 0,
5418 b: 0,
5419 a: 0,
5420 };
5421 img.paint_dot(2.0, 2.0, Brush::new(transparent, 2.0));
5422 assert!(img
5423 .pixels
5424 .get_u8_vec_ref()
5425 .expect("u8")
5426 .as_ref()
5427 .iter()
5428 .all(|b| *b == 0));
5429
5430 let mut img = rgba8_image(4, 4);
5432 let mut brush = Brush::new(opaque_red(), 2.0);
5433 brush.flow = -5.0;
5434 img.paint_dot(2.0, 2.0, brush);
5435 assert!(img
5436 .pixels
5437 .get_u8_vec_ref()
5438 .expect("u8")
5439 .as_ref()
5440 .iter()
5441 .all(|b| *b == 0));
5442 }
5443
5444 #[test]
5445 fn paint_stroke_paints_both_endpoints() {
5446 let mut img = rgba8_image(8, 8);
5447 img.paint_stroke(1.5, 1.5, 6.5, 6.5, Brush::new(opaque_red(), 1.5));
5448 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5449 let alpha_at = |x: usize, y: usize| px[(y * 8 + x) * 4 + 3];
5450 assert!(alpha_at(1, 1) > 0, "start of the stroke must be painted");
5451 assert!(alpha_at(6, 6) > 0, "end of the stroke must be painted");
5452 assert_eq!(alpha_at(7, 0), 0, "off-line pixels stay untouched");
5453 }
5454
5455 #[test]
5456 fn paint_stroke_zero_length_stamps_a_single_dab() {
5457 let mut img = rgba8_image(4, 4);
5459 img.paint_stroke(2.0, 2.0, 2.0, 2.0, Brush::new(opaque_red(), 2.0));
5460 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5461 let idx = (1 * 4 + 1) * 4;
5462 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255]);
5463 }
5464
5465 #[test]
5466 fn paint_stroke_degenerate_brush_params_do_not_divide_by_zero_or_hang() {
5467 for spacing in [0.0_f32, -1.0, f32::NAN] {
5470 let mut img = rgba8_image(8, 8);
5471 let mut brush = Brush::new(opaque_red(), 2.0);
5472 brush.spacing = spacing;
5473 img.paint_stroke(0.0, 0.0, 7.0, 7.0, brush);
5474 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(8 * 8 * 4));
5475 }
5476
5477 let mut img = rgba8_image(4, 4);
5479 img.paint_stroke(f32::NAN, 0.0, 1.0, 1.0, Brush::new(opaque_red(), 1.0));
5480 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(64));
5481
5482 let mut img = rgba8_image(4, 4);
5484 img.paint_stroke(0.0, 0.0, 3.0, 3.0, Brush::new(opaque_red(), 0.0));
5485 assert!(img
5486 .pixels
5487 .get_u8_vec_ref()
5488 .expect("u8")
5489 .as_ref()
5490 .iter()
5491 .all(|b| *b == 0));
5492 }
5493
5494 #[test]
5495 #[ignore = "BUG: an infinite stroke length makes n = i32::MAX -> ~2.1 billion \
5496 paint_dot calls (effectively an infinite loop). DO NOT un-ignore \
5497 without a length/step guard in paint_stroke."]
5498 fn bug_paint_stroke_with_infinite_endpoint_loops_2_billion_times() {
5499 let mut img = rgba8_image(4, 4);
5503 img.paint_stroke(0.0, 0.0, f32::INFINITY, 0.0, Brush::new(opaque_red(), 2.0));
5504 }
5505
5506 #[test]
5507 #[ignore = "BUG: paint_dot trusts self.width/height over the pixel buffer -> \
5508 index-out-of-bounds panic when they disagree"]
5509 fn bug_paint_dot_indexes_out_of_bounds_when_dims_exceed_the_buffer() {
5510 let mut img = RawImage {
5515 pixels: RawImageData::U8(vec![0u8; 4].into()),
5516 width: 100,
5517 height: 100,
5518 premultiplied_alpha: true,
5519 data_format: RawImageFormat::RGBA8,
5520 tag: Vec::new().into(),
5521 };
5522 img.paint_dot(50.0, 50.0, Brush::new(opaque_red(), 4.0));
5523 }
5524
5525 #[test]
5526 #[ignore = "BUG: into_loaded_image_source computes `width * height` (and then \
5527 `expected_len * channels`) without checked arithmetic -> overflow \
5528 panic instead of the documented None"]
5529 fn bug_into_loaded_image_source_overflows_on_huge_dimensions() {
5530 let img = RawImage {
5534 pixels: RawImageData::U8(vec![0u8; 4].into()),
5535 width: usize::MAX,
5536 height: 2,
5537 premultiplied_alpha: true,
5538 data_format: RawImageFormat::RGBA8,
5539 tag: Vec::new().into(),
5540 };
5541 assert!(img.into_loaded_image_source().is_none());
5542 }
5543}