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_isize =
1406 (font_size_px as isize).clamp(isize::MIN / 1000, isize::MAX / 1000);
1407 let font_size = StyleFontSize {
1408 inner: azul_css::props::basic::PixelValue::const_px(font_size_isize),
1409 };
1410
1411 let font_size_au = font_size_to_au(font_size);
1413
1414 let dpi_scale_factor = DpiScaleFactor {
1416 inner: FloatValue::new(dpi_scale),
1417 };
1418
1419 let font_family =
1421 css_property_cache.get_font_id_or_default(node_data, node_id, styled_node_state);
1422
1423 let font_families_hash = StyleFontFamiliesHash::new(font_family.as_ref());
1425
1426 self.get_font_instance_key(&font_families_hash, font_size_au, dpi_scale_factor)
1427 }
1428
1429 #[must_use] pub fn get_font_instance_key(
1430 &self,
1431 font_families_hash: &StyleFontFamiliesHash,
1432 font_size_au: Au,
1433 dpi_scale: DpiScaleFactor,
1434 ) -> Option<FontInstanceKey> {
1435 let font_family_hash = self.get_font_family(font_families_hash)?;
1436 let font_key = self.get_font_key(font_family_hash)?;
1437 let (_, instances) = self.get_registered_font(font_key)?;
1438 instances.get(&(font_size_au, dpi_scale)).copied()
1439 }
1440
1441 #[allow(dead_code)]
1472 fn remove_font_families_with_zero_references(&mut self) {
1473 let font_family_to_delete = self
1474 .font_id_map
1475 .iter()
1476 .filter_map(|(font_family, font_key)| {
1477 if self.currently_registered_fonts.contains_key(font_key) {
1478 None
1479 } else {
1480 Some(*font_family)
1481 }
1482 })
1483 .collect::<Vec<_>>();
1484
1485 for f in font_family_to_delete {
1486 self.font_id_map.remove(&f); }
1488
1489 let font_families_to_delete = self
1490 .font_families_map
1491 .iter()
1492 .filter_map(|(font_families, font_family)| {
1493 if self.font_id_map.contains_key(font_family) {
1494 None
1495 } else {
1496 Some(*font_families)
1497 }
1498 })
1499 .collect::<Vec<_>>();
1500
1501 for f in font_families_to_delete {
1502 self.font_families_map.remove(&f); }
1504 }
1505}
1506
1507#[derive(Debug, Clone)]
1518pub struct UpdateImageResult {
1519 pub key_to_update: ImageKey,
1520 pub new_descriptor: ImageDescriptor,
1521 pub new_image_data: ImageData,
1522}
1523
1524#[derive(Debug, Default)]
1525pub struct GlTextureCache {
1526 pub solved_textures:
1527 BTreeMap<DomId, BTreeMap<NodeId, (ImageKey, ImageDescriptor, ExternalImageId)>>,
1528 pub hashes: BTreeMap<(DomId, NodeId, ImageRefHash), ImageRefHash>,
1529}
1530
1531unsafe impl Send for GlTextureCache {}
1536
1537impl GlTextureCache {
1538 #[must_use] pub const fn empty() -> Self {
1540 Self {
1541 solved_textures: BTreeMap::new(),
1542 hashes: BTreeMap::new(),
1543 }
1544 }
1545
1546 pub fn update_texture(
1565 &mut self,
1566 dom_id: DomId,
1567 node_id: NodeId,
1568 document_id: DocumentId,
1569 epoch: Epoch,
1570 new_texture: Texture,
1571 insert_into_active_gl_textures_fn: &GlStoreImageFn,
1572 ) -> Option<ExternalImageId> {
1573 let new_descriptor = new_texture.get_descriptor();
1574 let di_map = self.solved_textures.get_mut(&dom_id)?;
1575 let entry = di_map.get_mut(&node_id)?;
1576
1577 entry.1 = new_descriptor;
1579
1580 let external_image_id = texture_external_image_id(dom_id, node_id);
1583 (insert_into_active_gl_textures_fn)(document_id, epoch, new_texture, external_image_id);
1584 entry.2 = external_image_id;
1585
1586 Some(external_image_id)
1587 }
1588}
1589
1590macro_rules! unique_id {
1591 ($struct_name:ident, $counter_name:ident) => {
1592 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
1593 #[repr(C)]
1594 pub struct $struct_name {
1595 pub id: usize,
1596 }
1597
1598 impl $struct_name {
1599 pub fn unique() -> Self {
1600 Self {
1601 id: $counter_name.fetch_add(1, AtomicOrdering::SeqCst),
1602 }
1603 }
1604 }
1605 };
1606}
1607
1608static PROPERTY_KEY_COUNTER: AtomicUsize = AtomicUsize::new(0);
1610unique_id!(TransformKey, PROPERTY_KEY_COUNTER);
1611unique_id!(ColorKey, PROPERTY_KEY_COUNTER);
1612unique_id!(OpacityKey, PROPERTY_KEY_COUNTER);
1613
1614static IMAGE_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
1615unique_id!(ImageId, IMAGE_ID_COUNTER);
1616static FONT_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
1617unique_id!(FontId, FONT_ID_COUNTER);
1618
1619#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1620#[repr(C)]
1621pub struct ImageMask {
1622 pub image: ImageRef,
1623 pub rect: LogicalRect,
1624 pub repeat: bool,
1625}
1626
1627impl_option!(
1628 ImageMask,
1629 OptionImageMask,
1630 copy = false,
1631 [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
1632);
1633
1634#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1635pub enum ImmediateFontId {
1636 Resolved((StyleFontFamilyHash, FontKey)),
1637 Unresolved(StyleFontFamilyVec),
1638}
1639
1640#[derive(Debug, Clone, PartialEq, PartialOrd)]
1641#[repr(C, u8)]
1642pub enum RawImageData {
1643 U8(U8Vec),
1645 U16(U16Vec),
1647 F32(F32Vec),
1649}
1650
1651impl RawImageData {
1652 #[must_use] pub const fn get_u8_vec_ref(&self) -> Option<&U8Vec> {
1653 match self {
1654 Self::U8(v) => Some(v),
1655 _ => None,
1656 }
1657 }
1658
1659 #[must_use] pub const fn get_u16_vec_ref(&self) -> Option<&U16Vec> {
1660 match self {
1661 Self::U16(v) => Some(v),
1662 _ => None,
1663 }
1664 }
1665
1666 #[must_use] pub const fn get_f32_vec_ref(&self) -> Option<&F32Vec> {
1667 match self {
1668 Self::F32(v) => Some(v),
1669 _ => None,
1670 }
1671 }
1672
1673 fn get_u8_vec(self) -> Option<U8Vec> {
1674 match self {
1675 Self::U8(v) => Some(v),
1676 _ => None,
1677 }
1678 }
1679
1680 fn get_u16_vec(self) -> Option<U16Vec> {
1681 match self {
1682 Self::U16(v) => Some(v),
1683 _ => None,
1684 }
1685 }
1686}
1687
1688#[derive(Debug, Clone, PartialEq, PartialOrd)]
1689#[repr(C)]
1690pub struct RawImage {
1691 pub pixels: RawImageData,
1692 pub width: usize,
1693 pub height: usize,
1694 pub premultiplied_alpha: bool,
1695 pub data_format: RawImageFormat,
1696 pub tag: U8Vec,
1697}
1698
1699#[repr(C)]
1705#[derive(Debug, Copy, Clone, PartialEq)]
1706pub struct Brush {
1707 pub color: ColorU,
1709 pub radius: f32,
1711 pub hardness: f32,
1714 pub flow: f32,
1717 pub spacing: f32,
1720}
1721
1722impl Brush {
1723 #[must_use] pub const fn new(color: ColorU, radius: f32) -> Self {
1725 Self {
1726 color,
1727 radius,
1728 hardness: 0.5,
1729 flow: 1.0,
1730 spacing: 0.25,
1731 }
1732 }
1733}
1734
1735#[allow(clippy::suboptimal_flops)] #[inline]
1742#[must_use] pub fn brush_dab_coverage(t: f32, hardness: f32) -> f32 {
1743 let edge0 = hardness.clamp(0.0, 1.0);
1744 let denom = (1.0 - edge0).max(1.0e-4);
1745 let x = ((t - edge0) / denom).clamp(0.0, 1.0);
1746 1.0 - (x * x * (3.0 - 2.0 * x))
1747}
1748
1749impl RawImage {
1750 #[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) {
1758 let r = brush.radius;
1759 #[allow(clippy::neg_cmp_op_on_partial_ord)]
1761 if !(r > 0.0) || self.width == 0 || self.height == 0 {
1762 return;
1763 }
1764 let bgr = match self.data_format {
1765 RawImageFormat::RGBA8 => false,
1766 RawImageFormat::BGRA8 => true,
1767 _ => return,
1768 };
1769 let (w, h) = (self.width as i32, self.height as i32);
1770 let buf: &mut [u8] = match self.pixels {
1771 RawImageData::U8(ref mut v) => v.as_mut(),
1772 _ => return,
1773 };
1774 let flow = brush.flow.clamp(0.0, 1.0) * (f32::from(brush.color.a) / 255.0);
1775 let (cr, cg, cb) = (
1776 f32::from(brush.color.r),
1777 f32::from(brush.color.g),
1778 f32::from(brush.color.b),
1779 );
1780 let x0 = (cx - r).floor().max(0.0) as i32;
1781 let y0 = (cy - r).floor().max(0.0) as i32;
1782 let x1 = ((cx + r).ceil() as i32).min(w);
1783 let y1 = ((cy + r).ceil() as i32).min(h);
1784 for y in y0..y1 {
1785 for x in x0..x1 {
1786 let dx = x as f32 + 0.5 - cx;
1787 let dy = y as f32 + 0.5 - cy;
1788 let dist = dx.hypot(dy);
1789 if dist > r {
1790 continue;
1791 }
1792 let a = brush_dab_coverage(dist / r, brush.hardness) * flow;
1793 if a <= 0.0 {
1794 continue;
1795 }
1796 let idx = ((y * w + x) as usize) * 4;
1797 if idx + 4 > buf.len() {
1801 continue;
1802 }
1803 let (ri, gi, bi, ai) = if bgr {
1804 (idx + 2, idx + 1, idx, idx + 3)
1805 } else {
1806 (idx, idx + 1, idx + 2, idx + 3)
1807 };
1808 let inv = 1.0 - a;
1809 buf[ri] = (cr * a + f32::from(buf[ri]) * inv).round().clamp(0.0, 255.0) as u8;
1810 buf[gi] = (cg * a + f32::from(buf[gi]) * inv).round().clamp(0.0, 255.0) as u8;
1811 buf[bi] = (cb * a + f32::from(buf[bi]) * inv).round().clamp(0.0, 255.0) as u8;
1812 buf[ai] =
1813 ((a + (f32::from(buf[ai]) / 255.0) * inv) * 255.0).round().clamp(0.0, 255.0) as u8;
1814 }
1815 }
1816 }
1817
1818 #[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) {
1824 let dx = x1 - x0;
1825 let dy = y1 - y0;
1826 let len = dx.hypot(dy);
1827 if !len.is_finite() {
1831 return;
1832 }
1833 let step = (brush.radius * brush.spacing.max(0.01)).max(0.5);
1834 let n = (len / step).floor() as i32;
1835 if n <= 0 {
1836 self.paint_dot(x1, y1, brush);
1837 return;
1838 }
1839 for i in 0..=n {
1840 let t = i as f32 / n as f32;
1841 self.paint_dot(x0 + dx * t, y0 + dy * t, brush);
1842 }
1843 }
1844}
1845
1846#[inline]
1851#[allow(clippy::cast_possible_truncation)] fn premultiply_alpha(array: &mut [u8]) {
1853 if array.len() != 4 {
1854 return;
1855 }
1856 let a = u32::from(array[3]);
1857 array[0] = (((u32::from(array[0]) * a) + 128) / 255) as u8;
1858 array[1] = (((u32::from(array[1]) * a) + 128) / 255) as u8;
1859 array[2] = (((u32::from(array[2]) * a) + 128) / 255) as u8;
1860}
1861
1862#[inline]
1863#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] fn normalize_u16(i: u16) -> u8 {
1866 ((f32::from(i) / f32::from(core::u16::MAX)) * f32::from(core::u8::MAX)) as u8
1867}
1868
1869const FOUR_BPP: usize = 4;
1870const TWO_CHANNELS: usize = 2;
1871const THREE_CHANNELS: usize = 3;
1872const FOUR_CHANNELS: usize = 4;
1873
1874impl RawImage {
1875 #[must_use] pub fn null_image() -> Self {
1877 Self {
1878 pixels: RawImageData::U8(Vec::new().into()),
1879 width: 0,
1880 height: 0,
1881 premultiplied_alpha: true,
1882 data_format: RawImageFormat::BGRA8,
1883 tag: Vec::new().into(),
1884 }
1885 }
1886
1887 #[allow(clippy::cast_sign_loss)] #[must_use] pub fn allocate_mask(size: LayoutSize) -> Self {
1890 Self {
1891 pixels: RawImageData::U8(
1892 vec![0; size.width.max(0) as usize * size.height.max(0) as usize].into(),
1893 ),
1894 width: size.width as usize,
1895 height: size.height as usize,
1896 premultiplied_alpha: true,
1897 data_format: RawImageFormat::R8,
1898 tag: Vec::new().into(),
1899 }
1900 }
1901
1902 #[must_use] pub fn into_loaded_image_source(self) -> Option<(ImageData, ImageDescriptor)> {
1908 let Self {
1909 width,
1910 height,
1911 pixels,
1912 data_format,
1913 premultiplied_alpha,
1914 tag,
1915 } = self;
1916
1917 let expected_len = width.checked_mul(height)?;
1920
1921 let (bytes, data_format, is_opaque): (U8Vec, RawImageFormat, bool) = match data_format {
1922 RawImageFormat::R8 => {
1923 let (bytes, is_opaque) = Self::load_r8(pixels, expected_len)?;
1924 (bytes, RawImageFormat::R8, is_opaque)
1925 }
1926 RawImageFormat::RG8 => {
1927 let (bytes, is_opaque) = Self::load_rg8(pixels, expected_len, premultiplied_alpha)?;
1928 (bytes, RawImageFormat::BGRA8, is_opaque)
1929 }
1930 RawImageFormat::RGB8 => {
1931 let (bytes, is_opaque) = Self::load_rgb8(pixels, expected_len)?;
1932 (bytes, RawImageFormat::BGRA8, is_opaque)
1933 }
1934 RawImageFormat::RGBA8 => {
1935 let (bytes, is_opaque) = Self::load_rgba8(pixels, expected_len, premultiplied_alpha)?;
1936 (bytes, RawImageFormat::BGRA8, is_opaque)
1937 }
1938 RawImageFormat::R16 => {
1939 let (bytes, is_opaque) = Self::load_r16(pixels, expected_len)?;
1940 (bytes, RawImageFormat::BGRA8, is_opaque)
1941 }
1942 RawImageFormat::RG16 => {
1943 let (bytes, is_opaque) = Self::load_rg16(pixels, expected_len)?;
1944 (bytes, RawImageFormat::BGRA8, is_opaque)
1945 }
1946 RawImageFormat::RGB16 => {
1947 let (bytes, is_opaque) = Self::load_rgb16(pixels, expected_len)?;
1948 (bytes, RawImageFormat::BGRA8, is_opaque)
1949 }
1950 RawImageFormat::RGBA16 => {
1951 let (bytes, is_opaque) =
1952 Self::load_rgba16(pixels, expected_len, premultiplied_alpha)?;
1953 (bytes, RawImageFormat::BGRA8, is_opaque)
1954 }
1955 RawImageFormat::BGR8 => {
1956 let (bytes, is_opaque) = Self::load_bgr8(pixels, expected_len)?;
1957 (bytes, RawImageFormat::BGRA8, is_opaque)
1958 }
1959 RawImageFormat::BGRA8 => {
1960 let (bytes, is_opaque) = Self::load_bgra8(pixels, expected_len, premultiplied_alpha)?;
1961 (bytes, RawImageFormat::BGRA8, is_opaque)
1962 }
1963 RawImageFormat::RGBF32 => {
1964 let (bytes, is_opaque) = Self::load_rgbf32(pixels, expected_len)?;
1965 (bytes, RawImageFormat::BGRA8, is_opaque)
1966 }
1967 RawImageFormat::RGBAF32 => {
1968 let (bytes, is_opaque) =
1969 Self::load_rgbaf32(pixels, expected_len, premultiplied_alpha)?;
1970 (bytes, RawImageFormat::BGRA8, is_opaque)
1971 }
1972 };
1973
1974 let image_data = ImageData::Raw(SharedRawImageData::new(bytes));
1975 let image_descriptor = ImageDescriptor {
1976 format: data_format,
1977 width,
1978 height,
1979 offset: 0,
1980 stride: None.into(),
1981 flags: ImageDescriptorFlags {
1982 is_opaque,
1983 allow_mipmaps: true,
1984 },
1985 };
1986
1987 Some((image_data, image_descriptor))
1988 }
1989
1990 fn load_r8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
1994 let pixels = pixels.get_u8_vec()?;
1995
1996 if pixels.len() != expected_len {
1997 return None;
1998 }
1999
2000 Some((pixels, false))
2001 }
2002
2003 fn load_rg8(
2004 pixels: RawImageData,
2005 expected_len: usize,
2006 premultiplied_alpha: bool,
2007 ) -> Option<(U8Vec, bool)> {
2008 let pixels = pixels.get_u8_vec()?;
2009
2010 if pixels.len() != expected_len * TWO_CHANNELS {
2011 return None;
2012 }
2013
2014 let mut is_opaque = true;
2015 let mut px = vec![0; expected_len * FOUR_BPP];
2016
2017 for (pixel_index, greyalpha) in pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate() {
2019 let grey = greyalpha[0];
2020 let alpha = greyalpha[1];
2021
2022 if alpha != 255 {
2023 is_opaque = false;
2024 }
2025
2026 px[pixel_index * FOUR_BPP] = grey;
2027 px[(pixel_index * FOUR_BPP) + 1] = grey;
2028 px[(pixel_index * FOUR_BPP) + 2] = grey;
2029 px[(pixel_index * FOUR_BPP) + 3] = alpha;
2030
2031 if !premultiplied_alpha {
2032 premultiply_alpha(
2033 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2034 );
2035 }
2036 }
2037
2038 Some((px.into(), is_opaque))
2039 }
2040
2041 fn load_rgb8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2042 let pixels = pixels.get_u8_vec()?;
2043
2044 if pixels.len() != expected_len * THREE_CHANNELS {
2045 return None;
2046 }
2047
2048 let mut px = vec![0; expected_len * FOUR_BPP];
2049
2050 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2052 let red = rgb[0];
2053 let green = rgb[1];
2054 let blue = rgb[2];
2055
2056 px[pixel_index * FOUR_BPP] = blue;
2057 px[(pixel_index * FOUR_BPP) + 1] = green;
2058 px[(pixel_index * FOUR_BPP) + 2] = red;
2059 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2060 }
2061
2062 Some((px.into(), true))
2063 }
2064
2065 fn load_rgba8(
2066 pixels: RawImageData,
2067 expected_len: usize,
2068 premultiplied_alpha: bool,
2069 ) -> Option<(U8Vec, bool)> {
2070 let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
2071
2072 if pixels.len() != expected_len * FOUR_CHANNELS {
2073 return None;
2074 }
2075
2076 let mut is_opaque = true;
2077
2078 if premultiplied_alpha {
2081 for rgba in pixels.chunks_exact_mut(4) {
2082 let (r, gba) = rgba.split_first_mut()?;
2083 core::mem::swap(r, gba.get_mut(1)?);
2084 let a = rgba.get_mut(3)?;
2085 if *a != 255 {
2086 is_opaque = false;
2087 }
2088 }
2089 } else {
2090 for rgba in pixels.chunks_exact_mut(4) {
2091 let (r, gba) = rgba.split_first_mut()?;
2093 core::mem::swap(r, gba.get_mut(1)?);
2094 let a = rgba.get_mut(3)?;
2095 if *a != 255 {
2096 is_opaque = false;
2097 }
2098 premultiply_alpha(rgba); }
2100 }
2101
2102 Some((pixels.into(), is_opaque))
2103 }
2104
2105 fn load_r16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2106 let pixels = pixels.get_u16_vec()?;
2107
2108 if pixels.len() != expected_len {
2109 return None;
2110 }
2111
2112 let mut px = vec![0; expected_len * FOUR_BPP];
2113
2114 for (pixel_index, grey_u16) in pixels.as_ref().iter().enumerate() {
2116 let grey_u8 = normalize_u16(*grey_u16);
2117 px[pixel_index * FOUR_BPP] = grey_u8;
2118 px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
2119 px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
2120 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2121 }
2122
2123 Some((px.into(), true))
2124 }
2125
2126 fn load_rg16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2127 let pixels = pixels.get_u16_vec()?;
2128
2129 if pixels.len() != expected_len * TWO_CHANNELS {
2130 return None;
2131 }
2132
2133 let mut is_opaque = true;
2134 let mut px = vec![0; expected_len * FOUR_BPP];
2135
2136 for (pixel_index, greyalpha) in pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate() {
2138 let grey_u8 = normalize_u16(greyalpha[0]);
2139 let alpha_u8 = normalize_u16(greyalpha[1]);
2140
2141 if alpha_u8 != 255 {
2142 is_opaque = false;
2143 }
2144
2145 px[pixel_index * FOUR_BPP] = grey_u8;
2146 px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
2147 px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
2148 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2149 }
2150
2151 Some((px.into(), is_opaque))
2152 }
2153
2154 fn load_rgb16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2155 let pixels = pixels.get_u16_vec()?;
2156
2157 if pixels.len() != expected_len * THREE_CHANNELS {
2158 return None;
2159 }
2160
2161 let mut px = vec![0; expected_len * FOUR_BPP];
2162
2163 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2165 let red_u8 = normalize_u16(rgb[0]);
2166 let green_u8 = normalize_u16(rgb[1]);
2167 let blue_u8 = normalize_u16(rgb[2]);
2168
2169 px[pixel_index * FOUR_BPP] = blue_u8;
2170 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2171 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2172 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2173 }
2174
2175 Some((px.into(), true))
2176 }
2177
2178 fn load_rgba16(
2179 pixels: RawImageData,
2180 expected_len: usize,
2181 premultiplied_alpha: bool,
2182 ) -> Option<(U8Vec, bool)> {
2183 let pixels = pixels.get_u16_vec()?;
2184
2185 if pixels.len() != expected_len * FOUR_CHANNELS {
2186 return None;
2187 }
2188
2189 let mut is_opaque = true;
2190 let mut px = vec![0; expected_len * FOUR_BPP];
2191
2192 if premultiplied_alpha {
2194 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2195 let red_u8 = normalize_u16(rgba[0]);
2196 let green_u8 = normalize_u16(rgba[1]);
2197 let blue_u8 = normalize_u16(rgba[2]);
2198 let alpha_u8 = normalize_u16(rgba[3]);
2199
2200 if alpha_u8 != 255 {
2201 is_opaque = false;
2202 }
2203
2204 px[pixel_index * FOUR_BPP] = blue_u8;
2205 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2206 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2207 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2208 }
2209 } else {
2210 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2211 let red_u8 = normalize_u16(rgba[0]);
2212 let green_u8 = normalize_u16(rgba[1]);
2213 let blue_u8 = normalize_u16(rgba[2]);
2214 let alpha_u8 = normalize_u16(rgba[3]);
2215
2216 if alpha_u8 != 255 {
2217 is_opaque = false;
2218 }
2219
2220 px[pixel_index * FOUR_BPP] = blue_u8;
2221 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2222 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2223 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2224 premultiply_alpha(
2225 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2226 );
2227 }
2228 }
2229
2230 Some((px.into(), is_opaque))
2231 }
2232
2233 fn load_bgr8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2234 let pixels = pixels.get_u8_vec()?;
2235
2236 if pixels.len() != expected_len * THREE_CHANNELS {
2237 return None;
2238 }
2239
2240 let mut px = vec![0; expected_len * FOUR_BPP];
2241
2242 for (pixel_index, bgr) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2244 let blue = bgr[0];
2245 let green = bgr[1];
2246 let red = bgr[2];
2247
2248 px[pixel_index * FOUR_BPP] = blue;
2249 px[(pixel_index * FOUR_BPP) + 1] = green;
2250 px[(pixel_index * FOUR_BPP) + 2] = red;
2251 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2252 }
2253
2254 Some((px.into(), true))
2255 }
2256
2257 fn load_bgra8(
2258 pixels: RawImageData,
2259 expected_len: usize,
2260 premultiplied_alpha: bool,
2261 ) -> Option<(U8Vec, bool)> {
2262 let mut is_opaque = true;
2263
2264 let bytes: U8Vec = if premultiplied_alpha {
2265 let pixels = pixels.get_u8_vec()?;
2267
2268 if pixels.len() != expected_len * FOUR_BPP {
2269 return None;
2270 }
2271
2272 is_opaque = pixels
2273 .as_ref()
2274 .chunks_exact(FOUR_CHANNELS)
2275 .all(|bgra| bgra[3] == 255);
2276
2277 pixels
2278 } else {
2279 let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
2280
2281 if pixels.len() != expected_len * FOUR_BPP {
2282 return None;
2283 }
2284
2285 for bgra in pixels.chunks_exact_mut(FOUR_CHANNELS) {
2286 if bgra[3] != 255 {
2287 is_opaque = false;
2288 }
2289 premultiply_alpha(bgra);
2290 }
2291 pixels.into()
2292 };
2293
2294 Some((bytes, is_opaque))
2295 }
2296
2297 #[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)> {
2301 let pixels = pixels.get_f32_vec_ref()?;
2302
2303 if pixels.len() != expected_len * THREE_CHANNELS {
2304 return None;
2305 }
2306
2307 let mut px = vec![0; expected_len * FOUR_BPP];
2308
2309 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2311 let red_u8 = (rgb[0] * 255.0) as u8;
2312 let green_u8 = (rgb[1] * 255.0) as u8;
2313 let blue_u8 = (rgb[2] * 255.0) as u8;
2314
2315 px[pixel_index * FOUR_BPP] = blue_u8;
2316 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2317 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2318 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2319 }
2320
2321 Some((px.into(), true))
2322 }
2323
2324 #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] #[allow(clippy::needless_pass_by_value)] fn load_rgbaf32(
2328 pixels: RawImageData,
2329 expected_len: usize,
2330 premultiplied_alpha: bool,
2331 ) -> Option<(U8Vec, bool)> {
2332 let pixels = pixels.get_f32_vec_ref()?;
2333
2334 if pixels.len() != expected_len * FOUR_CHANNELS {
2335 return None;
2336 }
2337
2338 let mut is_opaque = true;
2339 let mut px = vec![0; expected_len * FOUR_BPP];
2340
2341 if premultiplied_alpha {
2343 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2344 let red_u8 = (rgba[0] * 255.0) as u8;
2345 let green_u8 = (rgba[1] * 255.0) as u8;
2346 let blue_u8 = (rgba[2] * 255.0) as u8;
2347 let alpha_u8 = (rgba[3] * 255.0) as u8;
2348
2349 if alpha_u8 != 255 {
2350 is_opaque = false;
2351 }
2352
2353 px[pixel_index * FOUR_BPP] = blue_u8;
2354 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2355 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2356 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2357 }
2358 } else {
2359 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2360 let red_u8 = (rgba[0] * 255.0) as u8;
2361 let green_u8 = (rgba[1] * 255.0) as u8;
2362 let blue_u8 = (rgba[2] * 255.0) as u8;
2363 let alpha_u8 = (rgba[3] * 255.0) as u8;
2364
2365 if alpha_u8 != 255 {
2366 is_opaque = false;
2367 }
2368
2369 px[pixel_index * FOUR_BPP] = blue_u8;
2370 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2371 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2372 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2373 premultiply_alpha(
2374 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2375 );
2376 }
2377 }
2378
2379 Some((px.into(), is_opaque))
2380 }
2381}
2382
2383impl_option!(
2384 RawImage,
2385 OptionRawImage,
2386 copy = false,
2387 [Debug, Clone, PartialEq, PartialOrd]
2388);
2389
2390#[must_use] pub fn font_size_to_au(font_size: StyleFontSize) -> Au {
2391 Au::from_px(font_size.inner.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE))
2392}
2393
2394pub type FontInstanceFlags = u32;
2395
2396pub const FONT_INSTANCE_FLAG_SYNTHETIC_BOLD: u32 = 1 << 1;
2398pub const FONT_INSTANCE_FLAG_EMBEDDED_BITMAPS: u32 = 1 << 2;
2399pub const FONT_INSTANCE_FLAG_SUBPIXEL_BGR: u32 = 1 << 3;
2400pub const FONT_INSTANCE_FLAG_TRANSPOSE: u32 = 1 << 4;
2401pub const FONT_INSTANCE_FLAG_FLIP_X: u32 = 1 << 5;
2402pub const FONT_INSTANCE_FLAG_FLIP_Y: u32 = 1 << 6;
2403pub const FONT_INSTANCE_FLAG_SUBPIXEL_POSITION: u32 = 1 << 7;
2404
2405pub const FONT_INSTANCE_FLAG_FORCE_GDI: u32 = 1 << 16;
2407
2408pub const FONT_INSTANCE_FLAG_FONT_SMOOTHING: u32 = 1 << 16;
2410
2411pub const FONT_INSTANCE_FLAG_FORCE_AUTOHINT: u32 = 1 << 16;
2413pub const FONT_INSTANCE_FLAG_NO_AUTOHINT: u32 = 1 << 17;
2414pub const FONT_INSTANCE_FLAG_VERTICAL_LAYOUT: u32 = 1 << 18;
2415pub const FONT_INSTANCE_FLAG_LCD_VERTICAL: u32 = 1 << 19;
2416
2417#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2418pub struct GlyphOptions {
2419 pub render_mode: FontRenderMode,
2420 pub flags: FontInstanceFlags,
2421}
2422
2423#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2424pub enum FontRenderMode {
2425 Mono,
2426 Alpha,
2427 Subpixel,
2428}
2429
2430#[cfg(target_arch = "wasm32")]
2431#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2432pub struct FontInstancePlatformOptions {
2433 }
2435
2436#[cfg(target_os = "windows")]
2437#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2438pub struct FontInstancePlatformOptions {
2439 pub gamma: u16,
2440 pub contrast: u8,
2441 pub cleartype_level: u8,
2442}
2443
2444#[cfg(target_os = "macos")]
2445#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2446pub struct FontInstancePlatformOptions {
2447 pub unused: u32,
2448}
2449
2450#[cfg(target_os = "linux")]
2451#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2452pub struct FontInstancePlatformOptions {
2453 pub lcd_filter: FontLCDFilter,
2454 pub hinting: FontHinting,
2455}
2456
2457#[cfg(any(target_os = "android", target_os = "ios"))]
2461#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2462pub struct FontInstancePlatformOptions {
2463 pub unused: u32,
2464}
2465
2466#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2467pub enum FontHinting {
2468 None,
2469 Mono,
2470 Light,
2471 Normal,
2472 LCD,
2473}
2474
2475#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2476#[derive(Default)]
2477pub enum FontLCDFilter {
2478 None,
2479 #[default]
2480 Default,
2481 Light,
2482 Legacy,
2483}
2484
2485
2486#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2487pub struct FontInstanceOptions {
2488 pub render_mode: FontRenderMode,
2489 pub flags: FontInstanceFlags,
2490 pub bg_color: ColorU,
2491 pub synthetic_italics: SyntheticItalics,
2495}
2496
2497impl Default for FontInstanceOptions {
2498 fn default() -> Self {
2499 Self {
2500 render_mode: FontRenderMode::Subpixel,
2501 flags: 0,
2502 bg_color: ColorU::TRANSPARENT,
2503 synthetic_italics: SyntheticItalics::default(),
2504 }
2505 }
2506}
2507
2508#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2509#[derive(Default)]
2510pub struct SyntheticItalics {
2511 pub angle: i16,
2512}
2513
2514
2515#[derive(Debug)]
2521#[repr(C)]
2522pub struct SharedRawImageData {
2523 pub data: *const U8Vec,
2525 pub copies: *const AtomicUsize,
2527 pub run_destructor: bool,
2529}
2530
2531impl SharedRawImageData {
2532 #[must_use] pub fn new(data: U8Vec) -> Self {
2534 Self {
2535 data: Box::into_raw(Box::new(data)),
2536 copies: Box::into_raw(Box::new(AtomicUsize::new(1))),
2537 run_destructor: true,
2538 }
2539 }
2540
2541 #[must_use] pub fn as_ref(&self) -> &[u8] {
2543 unsafe { (*self.data).as_ref() }
2546 }
2547
2548 #[must_use] pub fn get_bytes(&self) -> &[u8] {
2550 self.as_ref()
2551 }
2552
2553 #[must_use] pub fn as_ptr(&self) -> *const u8 {
2555 unsafe { (*self.data).as_ref().as_ptr() }
2557 }
2558
2559 #[must_use] pub const fn len(&self) -> usize {
2561 unsafe { (*self.data).len() }
2563 }
2564
2565 #[must_use] pub const fn is_empty(&self) -> bool {
2567 self.len() == 0
2568 }
2569
2570 #[must_use] pub fn into_inner(self) -> Option<U8Vec> {
2573 unsafe {
2577 if self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) == Some(1) {
2578 let data = Box::from_raw(self.data.cast_mut());
2579 drop(Box::from_raw(self.copies.cast_mut()));
2580 core::mem::forget(self); Some(*data)
2582 } else {
2583 None
2584 }
2585 }
2586 }
2587}
2588
2589unsafe impl Send for SharedRawImageData {}
2592unsafe impl Sync for SharedRawImageData {}
2593
2594impl Clone for SharedRawImageData {
2595 fn clone(&self) -> Self {
2596 unsafe {
2599 self.copies
2600 .as_ref()
2601 .map(|m| m.fetch_add(1, AtomicOrdering::SeqCst));
2602 }
2603 Self {
2604 data: self.data,
2605 copies: self.copies,
2606 run_destructor: true,
2607 }
2608 }
2609}
2610
2611impl Drop for SharedRawImageData {
2612 fn drop(&mut self) {
2613 self.run_destructor = false;
2614 unsafe {
2618 let copies = (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst);
2619 if copies == 1 {
2620 drop(Box::from_raw(self.data.cast_mut()));
2621 drop(Box::from_raw(self.copies.cast_mut()));
2622 }
2623 }
2624 }
2625}
2626
2627impl PartialEq for SharedRawImageData {
2628 fn eq(&self, rhs: &Self) -> bool {
2629 core::ptr::eq(self.data, rhs.data)
2630 }
2631}
2632
2633impl Eq for SharedRawImageData {}
2634
2635impl PartialOrd for SharedRawImageData {
2636 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
2637 Some(self.cmp(other))
2638 }
2639}
2640
2641impl Ord for SharedRawImageData {
2642 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2643 (self.data as usize).cmp(&(other.data as usize))
2644 }
2645}
2646
2647impl Hash for SharedRawImageData {
2648 fn hash<H>(&self, state: &mut H)
2649 where
2650 H: Hasher,
2651 {
2652 (self.data as usize).hash(state);
2653 }
2654}
2655
2656#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2659#[repr(C, u8)]
2660pub enum ImageData {
2661 Raw(SharedRawImageData),
2664 External(ExternalImageData),
2667}
2668
2669#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
2671#[repr(C, u8)]
2672pub enum ExternalImageType {
2673 TextureHandle(ImageBufferKind),
2675 Buffer,
2677}
2678
2679#[repr(C)]
2683#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
2684pub struct ExternalImageId {
2685 pub inner: u64,
2686}
2687
2688static LAST_EXTERNAL_IMAGE_ID: AtomicUsize = AtomicUsize::new(0);
2689
2690impl Default for ExternalImageId {
2691 fn default() -> Self {
2692 Self::new()
2693 }
2694}
2695
2696impl ExternalImageId {
2697 pub fn new() -> Self {
2699 Self {
2700 inner: LAST_EXTERNAL_IMAGE_ID.fetch_add(1, AtomicOrdering::SeqCst) as u64,
2701 }
2702 }
2703}
2704
2705#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2706#[repr(C, u8)]
2707pub enum GlyphOutlineOperation {
2708 MoveTo(OutlineMoveTo),
2709 LineTo(OutlineLineTo),
2710 QuadraticCurveTo(OutlineQuadTo),
2711 CubicCurveTo(OutlineCubicTo),
2712 ClosePath,
2713}
2714
2715impl_option!(
2716 GlyphOutlineOperation,
2717 OptionGlyphOutlineOperation,
2718 copy = false,
2719 [Debug, Clone, PartialEq, Eq, PartialOrd]
2720);
2721
2722#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2724#[repr(C)]
2725pub struct OutlineMoveTo {
2726 pub x: i16,
2727 pub y: i16,
2728}
2729
2730#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2732#[repr(C)]
2733pub struct OutlineLineTo {
2734 pub x: i16,
2735 pub y: i16,
2736}
2737
2738#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2740#[repr(C)]
2741pub struct OutlineQuadTo {
2742 pub ctrl_1_x: i16,
2743 pub ctrl_1_y: i16,
2744 pub end_x: i16,
2745 pub end_y: i16,
2746}
2747
2748#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2750#[repr(C)]
2751pub struct OutlineCubicTo {
2752 pub ctrl_1_x: i16,
2753 pub ctrl_1_y: i16,
2754 pub ctrl_2_x: i16,
2755 pub ctrl_2_y: i16,
2756 pub end_x: i16,
2757 pub end_y: i16,
2758}
2759
2760#[derive(Debug, Clone, PartialEq, PartialOrd)]
2761#[repr(C)]
2762pub struct GlyphOutline {
2763 pub operations: GlyphOutlineOperationVec,
2764}
2765
2766azul_css::impl_vec!(GlyphOutlineOperation, GlyphOutlineOperationVec, GlyphOutlineOperationVecDestructor, GlyphOutlineOperationVecDestructorType, GlyphOutlineOperationVecSlice, OptionGlyphOutlineOperation);
2767azul_css::impl_vec_clone!(
2768 GlyphOutlineOperation,
2769 GlyphOutlineOperationVec,
2770 GlyphOutlineOperationVecDestructor
2771);
2772azul_css::impl_vec_debug!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2773azul_css::impl_vec_partialord!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2774azul_css::impl_vec_partialeq!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2775
2776#[derive(Debug, Clone, Copy)]
2777#[repr(C)]
2778pub struct OwnedGlyphBoundingBox {
2779 pub max_x: i16,
2780 pub max_y: i16,
2781 pub min_x: i16,
2782 pub min_y: i16,
2783}
2784
2785#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
2787#[repr(C)]
2788pub enum ImageBufferKind {
2789 Texture2D = 0,
2791 TextureRect = 1,
2798 TextureExternal = 2,
2803}
2804
2805#[repr(C)]
2807#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
2808pub struct ExternalImageData {
2809 pub id: ExternalImageId,
2811 pub channel_index: u8,
2814 pub image_type: ExternalImageType,
2816}
2817
2818pub type TileSize = u16;
2819
2820#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
2821pub enum ImageDirtyRect {
2822 All,
2823 Partial(LayoutRect),
2824}
2825
2826#[derive(Debug, Clone, PartialEq, PartialOrd)]
2827pub enum ResourceUpdate {
2828 AddFont(AddFont),
2829 DeleteFont(FontKey),
2830 AddFontInstance(AddFontInstance),
2831 DeleteFontInstance(FontInstanceKey),
2832 AddImage(AddImage),
2833 UpdateImage(UpdateImage),
2834 DeleteImage(ImageKey),
2835}
2836
2837#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2838pub struct AddImage {
2839 pub key: ImageKey,
2840 pub descriptor: ImageDescriptor,
2841 pub data: ImageData,
2842 pub tiling: Option<TileSize>,
2843}
2844
2845#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
2846pub struct UpdateImage {
2847 pub key: ImageKey,
2848 pub descriptor: ImageDescriptor,
2849 pub data: ImageData,
2850 pub dirty_rect: ImageDirtyRect,
2851}
2852
2853#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2856pub struct AddFont {
2857 pub key: FontKey,
2858 pub font: FontRef,
2859}
2860
2861impl fmt::Debug for AddFont {
2862 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2863 write!(
2864 f,
2865 "AddFont {{ key: {:?}, font: {:?} }}",
2866 self.key, self.font
2867 )
2868 }
2869}
2870
2871#[derive(Debug, Clone, PartialEq, PartialOrd)]
2872pub struct AddFontInstance {
2873 pub key: FontInstanceKey,
2874 pub font_key: FontKey,
2875 pub glyph_size: (Au, DpiScaleFactor),
2876 pub options: Option<FontInstanceOptions>,
2877 pub platform_options: Option<FontInstancePlatformOptions>,
2878 pub variations: Vec<FontVariation>,
2879}
2880
2881#[repr(C)]
2882#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
2883pub struct FontVariation {
2884 pub tag: u32,
2885 pub value: f32,
2886}
2887
2888#[repr(C)]
2889#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
2890pub struct Epoch {
2891 inner: u32,
2892}
2893
2894impl fmt::Display for Epoch {
2895 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2896 write!(f, "{}", self.inner)
2897 }
2898}
2899
2900impl Default for Epoch {
2901 fn default() -> Self {
2902 Self::new()
2903 }
2904}
2905
2906impl Epoch {
2907 #[must_use] pub const fn new() -> Self {
2911 Self { inner: 0 }
2912 }
2913 #[must_use] pub const fn from(i: u32) -> Self {
2914 Self { inner: i }
2915 }
2916 #[must_use] pub const fn into_u32(&self) -> u32 {
2917 self.inner
2918 }
2919
2920 pub const fn increment(&mut self) {
2923 use core::u32;
2924 const MAX_ID: u32 = u32::MAX - 1;
2925 *self = match self.inner {
2926 MAX_ID => Self { inner: 0 },
2927 other => Self {
2928 inner: other.saturating_add(1),
2929 },
2930 };
2931 }
2932}
2933
2934#[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Eq, Ord)]
2936pub struct Au(pub i32);
2937
2938pub const AU_PER_PX: i32 = 60;
2939pub const MAX_AU: i32 = (1 << 30) - 1;
2940pub const MIN_AU: i32 = -(1 << 30) - 1;
2941
2942impl Au {
2943 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] #[must_use] pub fn from_px(px: f32) -> Self {
2945 let target_app_units = (px * AU_PER_PX as f32) as i32;
2946 Self(target_app_units.clamp(MIN_AU, MAX_AU))
2947 }
2948 #[allow(clippy::cast_precision_loss)] #[must_use] pub fn into_px(&self) -> f32 {
2950 self.0 as f32 / AU_PER_PX as f32
2951 }
2952}
2953
2954#[derive(Debug)]
2956pub enum AddFontMsg {
2957 Font(FontKey, StyleFontFamilyHash, FontRef),
2959 Instance(AddFontInstance, (Au, DpiScaleFactor)),
2960}
2961
2962impl AddFontMsg {
2963 #[must_use] pub fn into_resource_update(&self) -> ResourceUpdate {
2964 use self::AddFontMsg::{Font, Instance};
2965 match self {
2966 Font(font_key, _, font_ref) => ResourceUpdate::AddFont(AddFont {
2967 key: *font_key,
2968 font: font_ref.clone(),
2969 }),
2970 Instance(fi, _) => ResourceUpdate::AddFontInstance(fi.clone()),
2971 }
2972 }
2973}
2974
2975#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
2976pub enum DeleteFontMsg {
2977 Font(FontKey),
2978 Instance(FontInstanceKey, (Au, DpiScaleFactor)),
2979}
2980
2981impl DeleteFontMsg {
2982 #[must_use] pub const fn into_resource_update(&self) -> ResourceUpdate {
2983 use self::DeleteFontMsg::{Font, Instance};
2984 match self {
2985 Font(f) => ResourceUpdate::DeleteFont(*f),
2986 Instance(fi, _) => ResourceUpdate::DeleteFontInstance(*fi),
2987 }
2988 }
2989}
2990
2991#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
2992pub struct AddImageMsg(pub AddImage);
2993
2994impl AddImageMsg {
2995 #[must_use] pub fn into_resource_update(&self) -> ResourceUpdate {
2996 ResourceUpdate::AddImage(self.0.clone())
2997 }
2998}
2999
3000#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3001#[repr(C)]
3002pub struct LoadedFontSource {
3003 pub data: U8Vec,
3004 pub index: u32,
3005 pub load_outlines: bool,
3006}
3007
3008pub type LoadFontFn = fn(&StyleFontFamily, &FcFontCache) -> Option<LoadedFontSource>;
3010
3011pub type ParseFontFn = fn(LoadedFontSource) -> Option<FontRef>; pub type GlStoreImageFn = fn(DocumentId, Epoch, Texture, ExternalImageId);
3015
3016#[must_use] pub fn texture_external_image_id(dom_id: DomId, node_id: NodeId) -> ExternalImageId {
3023 let dom = dom_id.inner as u64;
3024 let node = node_id.index() as u64;
3025 debug_assert!(u32::try_from(dom).is_ok(), "DomId exceeds 32-bit range");
3026 debug_assert!(u32::try_from(node).is_ok(), "NodeId exceeds 32-bit range");
3027 ExternalImageId {
3028 inner: (dom << 32) | (node & 0xFFFF_FFFF),
3029 }
3030}
3031
3032#[must_use] pub const fn image_ref_hash_to_external_image_id(hash: ImageRefHash) -> ExternalImageId {
3036 ExternalImageId {
3037 inner: hash.inner,
3038 }
3039}
3040
3041#[allow(clippy::too_many_lines)] pub fn build_add_font_resource_updates(
3051 renderer_resources: &mut RendererResources,
3052 dpi: DpiScaleFactor,
3053 fc_cache: &FcFontCache,
3054 id_namespace: IdNamespace,
3055 fonts_in_dom: &OrderedMap<ImmediateFontId, FastBTreeSet<Au>>,
3056 font_source_load_fn: LoadFontFn,
3057 parse_font_fn: ParseFontFn,
3058) -> Vec<(StyleFontFamilyHash, AddFontMsg)> {
3059 let mut resource_updates = Vec::new();
3060 let mut font_instances_added_this_frame = FastBTreeSet::new();
3061
3062 'outer: for (im_font_id, font_sizes) in fonts_in_dom {
3063 macro_rules! insert_font_instances {
3064 ($font_family_hash:expr, $font_key:expr, $font_size:expr) => {{
3065 let font_instance_key_exists = renderer_resources
3066 .currently_registered_fonts
3067 .get(&$font_key)
3068 .and_then(|(_, font_instances)| font_instances.get(&($font_size, dpi)))
3069 .is_some()
3070 || font_instances_added_this_frame.contains(&($font_key, ($font_size, dpi)));
3071
3072 if !font_instance_key_exists {
3073 let font_instance_key = FontInstanceKey::unique(id_namespace);
3074
3075 #[cfg(target_os = "windows")]
3077 let platform_options = FontInstancePlatformOptions {
3078 gamma: 300,
3079 contrast: 100,
3080 cleartype_level: 100,
3081 };
3082
3083 #[cfg(target_os = "linux")]
3084 let platform_options = FontInstancePlatformOptions {
3085 lcd_filter: FontLCDFilter::Default,
3086 hinting: FontHinting::Normal,
3087 };
3088
3089 #[cfg(target_os = "macos")]
3090 let platform_options = FontInstancePlatformOptions::default();
3091
3092 #[cfg(target_arch = "wasm32")]
3093 let platform_options = FontInstancePlatformOptions::default();
3094
3095 #[cfg(any(target_os = "android", target_os = "ios"))]
3096 let platform_options = FontInstancePlatformOptions::default();
3097
3098 let options = FontInstanceOptions {
3099 render_mode: FontRenderMode::Subpixel,
3100 flags: FONT_INSTANCE_FLAG_NO_AUTOHINT,
3101 ..Default::default()
3102 };
3103
3104 font_instances_added_this_frame.insert(($font_key, ($font_size, dpi)));
3105 resource_updates.push((
3106 $font_family_hash,
3107 AddFontMsg::Instance(
3108 AddFontInstance {
3109 key: font_instance_key,
3110 font_key: $font_key,
3111 glyph_size: ($font_size, dpi),
3112 options: Some(options),
3113 platform_options: Some(platform_options),
3114 variations: alloc::vec::Vec::new(),
3115 },
3116 ($font_size, dpi),
3117 ),
3118 ));
3119 }
3120 }};
3121 }
3122
3123 match im_font_id {
3124 ImmediateFontId::Resolved((font_family_hash, font_id)) => {
3125 for font_size in font_sizes {
3128 insert_font_instances!(*font_family_hash, *font_id, *font_size);
3129 }
3130 }
3131 ImmediateFontId::Unresolved(style_font_families) => {
3132 let mut font_family_hash = None;
3142 let font_families_hash = StyleFontFamiliesHash::new(style_font_families.as_ref());
3143
3144 'inner: for family in style_font_families.as_ref() {
3146 let current_family_hash = StyleFontFamilyHash::new(family);
3147
3148 if let Some(font_id) = renderer_resources.font_id_map.get(¤t_family_hash)
3149 {
3150 for font_size in font_sizes {
3152 insert_font_instances!(current_family_hash, *font_id, *font_size);
3153 }
3154 continue 'outer;
3155 }
3156
3157 let font_ref = match family {
3158 StyleFontFamily::Ref(r) => r.clone(), other => {
3160 let Some(font_data) = (font_source_load_fn)(other, fc_cache) else {
3162 continue 'inner;
3163 };
3164
3165
3166
3167 match (parse_font_fn)(font_data) {
3168 Some(s) => s,
3169 None => continue 'inner,
3170 }
3171 }
3172 };
3173
3174 font_family_hash = Some((current_family_hash, font_ref));
3176 break 'inner;
3177 }
3178
3179 let (font_family_hash, font_ref) = match font_family_hash {
3180 None => continue 'outer, Some(s) => s,
3182 };
3183
3184 let font_key = FontKey::unique(id_namespace);
3186 let add_font_msg = AddFontMsg::Font(font_key, font_family_hash, font_ref);
3187
3188 renderer_resources
3189 .font_id_map
3190 .insert(font_family_hash, font_key);
3191 renderer_resources
3192 .font_families_map
3193 .insert(font_families_hash, font_family_hash);
3194 resource_updates.push((font_family_hash, add_font_msg));
3195
3196 for font_size in font_sizes {
3198 insert_font_instances!(font_family_hash, font_key, *font_size);
3199 }
3200 }
3201 }
3202 }
3203
3204 resource_updates
3205}
3206
3207#[allow(unused_variables)]
3223pub fn build_add_image_resource_updates(
3224 renderer_resources: &RendererResources,
3225 id_namespace: IdNamespace,
3226 epoch: Epoch,
3227 document_id: &DocumentId,
3228 images_in_dom: &FastBTreeSet<ImageRef>,
3229 insert_into_active_gl_textures: GlStoreImageFn,
3230) -> Vec<(ImageRefHash, AddImageMsg)> {
3231 images_in_dom
3232 .iter()
3233 .filter_map(|image_ref| {
3234 let image_ref_hash = image_ref_get_hash(image_ref);
3235
3236 if renderer_resources
3237 .currently_registered_images
3238 .contains_key(&image_ref_hash)
3239 {
3240 return None;
3241 }
3242
3243 match image_ref.get_data() {
3246 DecodedImage::Gl(texture) => {
3247 let descriptor = texture.get_descriptor();
3248 let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
3249 let external_image_id = image_ref_hash_to_external_image_id(image_ref_hash);
3253 (insert_into_active_gl_textures)(
3255 *document_id,
3256 epoch,
3257 texture.clone(),
3258 external_image_id,
3259 );
3260 Some((
3261 image_ref_hash,
3262 AddImageMsg(AddImage {
3263 key,
3264 data: ImageData::External(ExternalImageData {
3265 id: external_image_id,
3266 channel_index: 0,
3267 image_type: ExternalImageType::TextureHandle(
3268 ImageBufferKind::Texture2D,
3269 ),
3270 }),
3271 descriptor,
3272 tiling: None,
3273 }),
3274 ))
3275 }
3276 DecodedImage::Raw((descriptor, data)) => {
3277 let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
3278 Some((
3279 image_ref_hash,
3280 AddImageMsg(AddImage {
3281 key,
3282 data: data.clone(), descriptor: *descriptor, tiling: None,
3286 }),
3287 ))
3288 }
3289 DecodedImage::NullImage { .. } | DecodedImage::Callback(_) => None,
3292 }
3293 })
3294 .collect()
3295}
3296
3297#[allow(clippy::needless_pass_by_value)] pub fn add_resources(
3304 renderer_resources: &mut RendererResources,
3305 all_resource_updates: &mut Vec<ResourceUpdate>,
3306 add_font_resources: Vec<(StyleFontFamilyHash, AddFontMsg)>,
3307 add_image_resources: Vec<(ImageRefHash, AddImageMsg)>,
3308) {
3309 all_resource_updates.extend(
3310 add_font_resources
3311 .iter()
3312 .map(|(_, f)| f.into_resource_update()),
3313 );
3314 all_resource_updates.extend(
3315 add_image_resources
3316 .iter()
3317 .map(|(_, i)| i.into_resource_update()),
3318 );
3319
3320 for (image_ref_hash, add_image_msg) in &add_image_resources {
3321 renderer_resources.currently_registered_images.insert(
3322 *image_ref_hash,
3323 ResolvedImage {
3324 key: add_image_msg.0.key,
3325 descriptor: add_image_msg.0.descriptor,
3326 },
3327 );
3328 renderer_resources
3331 .image_key_map
3332 .insert(add_image_msg.0.key, *image_ref_hash);
3333 }
3334
3335 for (_, add_font_msg) in add_font_resources {
3336 use self::AddFontMsg::{Font, Instance};
3337 match add_font_msg {
3338 Font(fk, font_family_hash, font_ref) => {
3339 renderer_resources
3340 .currently_registered_fonts
3341 .entry(fk)
3342 .or_insert_with(|| (font_ref.clone(), OrderedMap::default()));
3343
3344 renderer_resources
3346 .font_hash_map
3347 .insert(font_ref.get_hash(), fk);
3348 }
3349 Instance(fi, size) => {
3350 if let Some((_, instances)) = renderer_resources
3351 .currently_registered_fonts
3352 .get_mut(&fi.font_key)
3353 {
3354 instances.insert(size, fi.key);
3355 }
3356 }
3357 }
3358 }
3359}
3360
3361#[cfg(test)]
3362#[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 {
3364 use super::*;
3365
3366 #[test]
3367 fn normalize_u16_maps_full_range() {
3368 assert_eq!(normalize_u16(0), 0);
3370 assert_eq!(normalize_u16(u16::MAX), 255);
3371 let mid = normalize_u16(u16::MAX / 2);
3373 assert!((126..=128).contains(&mid), "midpoint normalized to {mid}");
3374 assert_eq!(normalize_u16(256), 0);
3377 assert_eq!(normalize_u16(257), 1);
3378 }
3379
3380 #[test]
3381 fn load_bgra8_rejects_wrong_length() {
3382 let short = RawImageData::U8(vec![0u8; 4 * 3].into()); assert!(RawImage::load_bgra8(short, 4, true).is_none());
3386
3387 let ok = RawImageData::U8(vec![255u8; 4 * 4].into()); assert!(RawImage::load_bgra8(ok, 4, true).is_some());
3390
3391 let short2 = RawImageData::U8(vec![0u8; 4 * 2].into());
3393 assert!(RawImage::load_bgra8(short2, 4, false).is_none());
3394 }
3395
3396 #[test]
3399 fn imageref_get_data_reads_backing_box() {
3400 let img = ImageRef::null_image(2, 3, RawImageFormat::RGBA8, vec![7, 8]);
3402 match img.get_data() {
3403 DecodedImage::NullImage { width, height, tag, .. } => {
3404 assert_eq!((*width, *height), (2, 3));
3405 assert_eq!(tag.as_slice(), &[7, 8]);
3406 }
3407 _ => panic!("expected NullImage"),
3408 }
3409 }
3410
3411 #[test]
3412 fn imageref_clone_shares_refcount_and_identity() {
3413 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3416 let c = img.clone();
3417 assert_eq!(img, c); assert!(c.into_inner().is_none());
3420 assert!(img.into_inner().is_some());
3422 }
3423
3424 #[test]
3425 fn imageref_deep_copy_has_distinct_identity() {
3426 let img = ImageRef::null_image(4, 4, RawImageFormat::RGBA8, vec![1]);
3429 let d = img.deep_copy();
3430 assert_ne!(img, d);
3431 drop(img);
3432 assert_eq!(d.get_size().width as usize, 4);
3434 }
3435
3436 #[test]
3437 fn imageref_last_drop_frees_once() {
3438 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3441 let c = img.clone();
3442 drop(img);
3443 drop(c);
3444 }
3445
3446 #[test]
3447 fn imageref_get_callback_none_for_non_callback_and_when_shared() {
3448 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3451 assert!(img.get_image_callback().is_none());
3452 let c = img.clone();
3453 assert!(img.get_image_callback().is_none()); drop(c);
3455 }
3456
3457 #[test]
3458 fn shared_raw_image_data_read_paths() {
3459 let s = SharedRawImageData::new(vec![10u8, 20, 30].into());
3461 assert_eq!(s.as_ref(), &[10, 20, 30]);
3462 assert_eq!(s.len(), 3);
3463 assert!(!s.is_empty());
3464 assert_eq!(unsafe { *s.as_ptr() }, 10);
3465 assert!(SharedRawImageData::new(Vec::<u8>::new().into()).is_empty());
3466 }
3467
3468 #[test]
3469 fn shared_raw_image_data_clone_shares_alloc() {
3470 let s = SharedRawImageData::new(vec![1u8, 2, 3, 4].into());
3473 let c = s.clone();
3474 assert_eq!(s, c); assert_eq!(s.as_ptr(), c.as_ptr());
3476 assert!(c.into_inner().is_none()); let inner = s.into_inner().expect("sole owner extraction");
3478 assert_eq!(inner.as_ref(), &[1, 2, 3, 4]);
3479 }
3480
3481 #[test]
3482 fn shared_raw_image_data_last_drop_frees_once() {
3483 let s = SharedRawImageData::new(vec![0u8; 8].into());
3485 let c = s.clone();
3486 drop(s);
3487 drop(c);
3488 }
3489}
3490
3491#[cfg(test)]
3492#[allow(
3493 clippy::float_cmp,
3494 clippy::items_after_statements,
3495 clippy::redundant_clone,
3496 clippy::cast_possible_truncation,
3497 clippy::cast_precision_loss,
3498 clippy::cast_sign_loss,
3499 clippy::cast_lossless,
3500 clippy::unreadable_literal,
3501 clippy::too_many_lines,
3502 clippy::many_single_char_names,
3503 clippy::similar_names,
3504 unused_qualifications,
3505 unreachable_pub,
3506 private_interfaces
3507)] mod autotest_generated {
3509 use alloc::string::String;
3510
3511 use super::*;
3512
3513 fn dummy_font_ref() -> FontRef {
3522 static DUMMY_FONT_DATA: u8 = 0;
3523 extern "C" fn dummy_destructor(_: *mut core::ffi::c_void) {}
3524 FontRef::new(
3525 core::ptr::addr_of!(DUMMY_FONT_DATA).cast::<core::ffi::c_void>(),
3526 dummy_destructor,
3527 )
3528 }
3529
3530 fn load_font_none(_: &StyleFontFamily, _: &FcFontCache) -> Option<LoadedFontSource> {
3532 None
3533 }
3534
3535 fn parse_font_none(_: LoadedFontSource) -> Option<FontRef> {
3537 None
3538 }
3539
3540 fn store_gl_texture_noop(_: DocumentId, _: Epoch, _: Texture, _: ExternalImageId) {}
3542
3543 fn test_document_id() -> DocumentId {
3544 DocumentId {
3545 namespace_id: IdNamespace(7),
3546 id: 0,
3547 }
3548 }
3549
3550 fn rgba8_image(w: usize, h: usize) -> RawImage {
3552 RawImage {
3553 pixels: RawImageData::U8(vec![0u8; w * h * 4].into()),
3554 width: w,
3555 height: h,
3556 premultiplied_alpha: true,
3557 data_format: RawImageFormat::RGBA8,
3558 tag: Vec::new().into(),
3559 }
3560 }
3561
3562 fn opaque_red() -> ColorU {
3563 ColorU {
3564 r: 255,
3565 g: 0,
3566 b: 0,
3567 a: 255,
3568 }
3569 }
3570
3571 #[test]
3576 fn match_route_valid_minimal_positive_control() {
3577 let m = match_route("/user/:id", "/user/42").expect("documented example must match");
3579 assert_eq!(m.pattern.as_str(), "/user/:id");
3580 assert_eq!(m.get_param("id").map(AzString::as_str), Some("42"));
3581
3582 let root = match_route("/", "/").expect("root must match root");
3583 assert!(root.params.as_ref().is_empty());
3584
3585 assert!(match_route("/about", "/settings").is_none());
3586 }
3587
3588 #[test]
3589 fn match_route_empty_input_does_not_panic() {
3590 let m = match_route("", "").expect("empty vs empty is a zero-segment match");
3593 assert!(m.params.as_ref().is_empty());
3594 assert!(match_route("", "/").is_some()); assert!(match_route("/", "").is_some());
3596 assert!(match_route("", "/a").is_none()); assert!(match_route("/a", "").is_none());
3598 }
3599
3600 #[test]
3601 fn match_route_whitespace_only_is_not_trimmed() {
3602 assert!(match_route(" ", " ").is_some());
3605 assert!(match_route(" ", "\t\n").is_none());
3606 assert!(match_route("/ ", "/").is_none()); assert!(match_route("/\t\n", "/\t\n").is_some());
3608 }
3609
3610 #[test]
3611 fn match_route_garbage_never_panics() {
3612 for pat in [
3613 "\0\0\0",
3614 "///////",
3615 "::::",
3616 ":",
3617 "%%%$#@!",
3618 "\u{feff}",
3619 "/a/../../etc/passwd",
3620 ] {
3621 for path in ["", "/", "\0", "/a/b/c", "%%%$#@!", "\u{feff}"] {
3622 let _ = match_route(pat, path);
3624 }
3625 }
3626 assert!(match_route("///////", "/").is_some());
3628 let m = match_route("/:", "/hello").expect("empty param name still matches");
3630 assert_eq!(m.get_param("").map(AzString::as_str), Some("hello"));
3631 }
3632
3633 #[test]
3634 fn match_route_leading_trailing_junk_is_rejected_or_ignored() {
3635 assert!(match_route("/user/:id/", "/user/42").is_some());
3638 assert!(match_route("/user/:id", "/user/42/").is_some());
3639 assert!(match_route(" /about ", "/about").is_none());
3641 assert!(match_route("/about", "/about;garbage").is_none());
3642 }
3643
3644 #[test]
3645 fn match_route_boundary_number_strings_are_opaque_segments() {
3646 for v in [
3648 "0",
3649 "-0",
3650 "9223372036854775807",
3651 "-9223372036854775808",
3652 "1e400",
3653 "NaN",
3654 "inf",
3655 "-inf",
3656 "0.0000000000000000001",
3657 ] {
3658 let path = String::from("/user/") + v;
3659 let m = match_route("/user/:id", &path).expect("any segment matches a :param");
3660 assert_eq!(m.get_param("id").map(AzString::as_str), Some(v));
3661 }
3662 }
3663
3664 #[test]
3665 fn match_route_unicode_multibyte_does_not_panic() {
3666 let m = match_route("/user/:id", "/user/\u{1F600}").expect("emoji segment matches");
3668 assert_eq!(m.get_param("id").map(AzString::as_str), Some("\u{1F600}"));
3669
3670 let m = match_route("/:é\u{0301}", "/e\u{0301}\u{202E}x").expect("unicode param name");
3672 assert_eq!(
3673 m.get_param("é\u{0301}").map(AzString::as_str),
3674 Some("e\u{0301}\u{202E}x")
3675 );
3676 assert!(match_route("/é", "/e\u{0301}").is_none());
3678 }
3679
3680 #[test]
3681 fn match_route_extremely_long_input_does_not_hang() {
3682 let huge = String::from("/") + &"a".repeat(1_000_000);
3684 assert!(match_route("/x", &huge).is_none());
3685 let m = match_route("/:id", &huge).expect("one long segment is still one segment");
3686 assert_eq!(m.get_param("id").map(|s| s.as_str().len()), Some(1_000_000));
3687 }
3688
3689 #[test]
3690 fn match_route_deeply_nested_input_does_not_stack_overflow() {
3691 let deep = "/a".repeat(10_000);
3693 let m = match_route(&deep, &deep).expect("identical deep paths match");
3694 assert!(m.params.as_ref().is_empty());
3695
3696 let all_params = "/:p".repeat(10_000);
3697 let m = match_route(&all_params, &deep).expect("10k params extract");
3698 assert_eq!(m.params.as_ref().len(), 10_000);
3699 assert_eq!(m.get_param("p").map(AzString::as_str), Some("a"));
3701
3702 let brackets = String::from("/") + &"[".repeat(10_000);
3703 assert!(match_route("/:x", &brackets).is_some());
3704 }
3705
3706 #[test]
3707 fn match_route_segment_count_mismatch_is_none() {
3708 assert!(match_route("/a/:b", "/a").is_none());
3709 assert!(match_route("/a", "/a/b").is_none());
3710 assert!(match_route("/:a/:b/:c", "/1/2").is_none());
3711 }
3712
3713 #[test]
3714 fn route_match_get_param_missing_keys_return_none() {
3715 let empty = RouteMatch {
3716 pattern: AzString::from_const_str("/"),
3717 params: StringPairVec::from_vec(Vec::new()),
3718 };
3719 assert!(empty.get_param("").is_none());
3721 assert!(empty.get_param(" ").is_none());
3722 assert!(empty.get_param("\t\n").is_none());
3723 assert!(empty.get_param("\u{1F600}").is_none());
3724 assert!(empty.get_param("\0").is_none());
3725 assert!(empty.get_param(&"k".repeat(100_000)).is_none());
3726
3727 let m = match_route("/u/:id", "/u/7").expect("valid");
3729 assert_eq!(m.get_param("id").map(AzString::as_str), Some("7"));
3730 assert!(m.get_param("ID").is_none()); assert!(m.get_param("i").is_none()); assert!(m.get_param(":id").is_none()); }
3734
3735 #[test]
3736 fn app_config_match_route_for_path_adversarial_inputs() {
3737 let mut config = AppConfig::create();
3738 let cb: crate::callbacks::LayoutCallbackType = autotest_layout;
3739 extern "C" fn autotest_layout(
3740 _: RefAny,
3741 _: crate::callbacks::LayoutCallbackInfo,
3742 ) -> crate::dom::Dom {
3743 crate::dom::Dom::create_body()
3744 }
3745 config.add_route(AzString::from_const_str("/"), cb);
3746 config.add_route(AzString::from_const_str("/user/:id"), cb);
3747
3748 let (route, m) = config
3750 .match_route_for_path("/user/42")
3751 .expect("registered route must match");
3752 assert_eq!(route.pattern.as_str(), "/user/:id");
3753 assert_eq!(m.get_param("id").map(AzString::as_str), Some("42"));
3754
3755 assert!(config.match_route_for_path("").is_some());
3757 assert!(config.match_route_for_path("/").is_some());
3758
3759 assert!(config.match_route_for_path("/nope/nope/nope").is_none());
3761 assert!(config.match_route_for_path("\0\0").is_none());
3762 assert!(config.match_route_for_path(" ").is_none());
3763 let long = String::from("/user/") + &"9".repeat(500_000);
3764 assert!(config.match_route_for_path(&long).is_some());
3765 let m = config
3766 .match_route_for_path("/user/\u{1F600}")
3767 .expect("unicode param");
3768 assert_eq!(m.1.get_param("id").map(AzString::as_str), Some("\u{1F600}"));
3769 }
3770
3771 #[test]
3772 fn app_config_add_route_replaces_same_pattern_and_orders_by_insertion() {
3773 extern "C" fn layout_a(_: RefAny, _: crate::callbacks::LayoutCallbackInfo) -> crate::dom::Dom {
3774 crate::dom::Dom::create_body()
3775 }
3776 let cb: crate::callbacks::LayoutCallbackType = layout_a;
3777
3778 let mut config = AppConfig::create();
3779 assert!(config.routes.as_ref().is_empty());
3780 config.add_route(AzString::from_const_str("/dup"), cb);
3781 config.add_route(AzString::from_const_str("/dup"), cb);
3782 assert_eq!(config.routes.as_ref().len(), 1, "same pattern must replace");
3783
3784 let mut config = AppConfig::create();
3786 config.add_route(AzString::from_const_str("/:anything"), cb);
3787 config.add_route(AzString::from_const_str("/about"), cb);
3788 let (route, _) = config.match_route_for_path("/about").expect("matches");
3789 assert_eq!(route.pattern.as_str(), "/:anything");
3790 }
3791
3792 #[test]
3797 fn dpi_scale_factor_new_handles_nan_and_infinities() {
3798 assert_eq!(DpiScaleFactor::new(0.0).inner.get(), 0.0);
3800 assert_eq!(DpiScaleFactor::new(1.0).inner.get(), 1.0);
3801 assert_eq!(DpiScaleFactor::new(f32::NAN).inner.get(), 0.0);
3802 assert!(DpiScaleFactor::new(f32::INFINITY).inner.get().is_finite());
3803 assert!(DpiScaleFactor::new(f32::NEG_INFINITY).inner.get().is_finite());
3804 assert!(DpiScaleFactor::new(f32::MAX).inner.get().is_finite());
3805 assert!(DpiScaleFactor::new(f32::MIN).inner.get().is_finite());
3806 assert_eq!(DpiScaleFactor::new(f32::MIN_POSITIVE).inner.get(), 0.0);
3808 assert_eq!(DpiScaleFactor::new(1.5), DpiScaleFactor::new(1.5));
3810 assert_ne!(DpiScaleFactor::new(1.5), DpiScaleFactor::new(2.0));
3811 }
3812
3813 #[test]
3814 fn named_font_new_keeps_fields_verbatim() {
3815 let f = NamedFont::new(
3816 AzString::from_const_str(""),
3817 U8Vec::from_vec(Vec::new()),
3818 );
3819 assert_eq!(f.name.as_str(), "");
3820 assert!(f.bytes.as_ref().is_empty());
3821
3822 let bytes = vec![0u8, 255, 128];
3823 let f = NamedFont::new(AzString::from(String::from("\u{1F600}")), bytes.clone().into());
3824 assert_eq!(f.name.as_str(), "\u{1F600}");
3825 assert_eq!(f.bytes.as_ref(), bytes.as_slice());
3826 }
3827
3828 #[test]
3829 fn loaded_font_new_keeps_fields_verbatim_at_limits() {
3830 let f = LoadedFont::new(0, AzString::from_const_str(""), 0, false);
3831 assert_eq!(f.font_hash, 0);
3832 assert_eq!(f.num_glyphs, 0);
3833 assert!(!f.has_bytes);
3834
3835 let f = LoadedFont::new(u64::MAX, AzString::from_const_str("x"), u32::MAX, true);
3836 assert_eq!(f.font_hash, u64::MAX);
3837 assert_eq!(f.num_glyphs, u32::MAX);
3838 assert!(f.has_bytes);
3839 }
3840
3841 #[test]
3842 fn brush_new_defaults_and_extreme_radius() {
3843 let b = Brush::new(opaque_red(), 4.0);
3844 assert_eq!(b.radius, 4.0);
3845 assert_eq!(b.hardness, 0.5);
3846 assert_eq!(b.flow, 1.0);
3847 assert_eq!(b.spacing, 0.25);
3848 assert_eq!(b.color, opaque_red());
3849
3850 assert!(Brush::new(opaque_red(), f32::NAN).radius.is_nan());
3852 assert_eq!(Brush::new(opaque_red(), -0.0).radius, -0.0);
3853 assert_eq!(Brush::new(opaque_red(), f32::INFINITY).radius, f32::INFINITY);
3854 }
3855
3856 #[test]
3857 fn image_cache_new_is_empty_and_default_is_neutral() {
3858 let cache = ImageCache::new();
3859 assert!(cache.image_id_map.is_empty());
3860 assert!(ImageCache::default().image_id_map.is_empty());
3861 }
3862
3863 #[test]
3864 fn gl_texture_cache_empty_is_neutral() {
3865 let cache = GlTextureCache::empty();
3866 assert!(cache.solved_textures.is_empty());
3867 assert!(cache.hashes.is_empty());
3868 let d = GlTextureCache::default();
3869 assert!(d.solved_textures.is_empty());
3870 assert!(d.hashes.is_empty());
3871 }
3872
3873 #[test]
3874 fn external_image_id_new_is_monotonic() {
3875 let a = ExternalImageId::new();
3876 let b = ExternalImageId::new();
3877 assert!(b.inner > a.inner, "the counter must strictly increase");
3878 assert!(ExternalImageId::default().inner > b.inner);
3879 }
3880
3881 #[test]
3882 fn shared_raw_image_data_new_invariants() {
3883 let empty = SharedRawImageData::new(U8Vec::from_vec(Vec::new()));
3884 assert_eq!(empty.len(), 0);
3885 assert!(empty.is_empty());
3886 assert!(empty.as_ref().is_empty());
3887 assert!(empty.get_bytes().is_empty());
3888 assert!(!empty.as_ptr().is_null());
3890
3891 let big = SharedRawImageData::new(vec![7u8; 100_000].into());
3892 assert_eq!(big.len(), 100_000);
3893 assert!(!big.is_empty());
3894 assert_eq!(big.as_ref().len(), big.len());
3895 assert_eq!(big.get_bytes(), big.as_ref());
3896 assert_eq!(big.into_inner().expect("sole owner").as_ref().len(), 100_000);
3898 }
3899
3900 #[test]
3901 fn app_config_create_registers_builtins_and_defaults() {
3902 let config = AppConfig::create();
3903 assert_eq!(config.log_level, AppLogLevel::Error);
3904 assert!(!config.enable_visual_panic_hook);
3905 assert!(config.enable_logging_on_panic);
3906 assert_eq!(config.termination_behavior, AppTerminationBehavior::EndProcess);
3907 assert!(config.routes.as_ref().is_empty());
3908 assert!(matches!(
3909 config.mock_css_environment,
3910 OptionCssMockEnvironment::None
3911 ));
3912 let libs = config.component_libraries.as_ref();
3914 assert_eq!(libs.len(), 1);
3915 assert_eq!(libs[0].name.as_str(), "builtin");
3916 assert!(!libs[0].components.as_ref().is_empty());
3917 }
3918
3919 #[test]
3920 fn app_config_add_component_library_replaces_same_name() {
3921 let register: crate::xml::RegisterComponentLibraryFnType =
3922 crate::xml::register_builtin_components;
3923 let mut config = AppConfig::create();
3924 let n_builtin = config.component_libraries.as_ref()[0].components.as_ref().len();
3925
3926 config.add_component_library(AzString::from_const_str("builtin"), register);
3928 assert_eq!(config.component_libraries.as_ref().len(), 1);
3929 assert_eq!(
3930 config.component_libraries.as_ref()[0].components.as_ref().len(),
3931 n_builtin
3932 );
3933
3934 config.add_component_library(AzString::from_const_str(""), register);
3936 config.add_component_library(AzString::from_const_str("\u{1F600}"), register);
3937 assert_eq!(config.component_libraries.as_ref().len(), 3);
3938 assert_eq!(config.component_libraries.as_ref()[2].name.as_str(), "\u{1F600}");
3939 }
3940
3941 #[test]
3942 fn app_config_with_mock_environment_sets_the_option() {
3943 let config = AppConfig::create().with_mock_environment(CssMockEnvironment::dark_theme());
3944 match config.mock_css_environment {
3945 OptionCssMockEnvironment::Some(env) => {
3946 assert!(matches!(
3947 env.theme,
3948 azul_css::dynamic_selector::OptionThemeCondition::Some(
3949 azul_css::dynamic_selector::ThemeCondition::Dark
3950 )
3951 ));
3952 }
3953 OptionCssMockEnvironment::None => panic!("mock env must be Some"),
3954 }
3955 let config = AppConfig::create()
3957 .with_mock_environment(CssMockEnvironment::linux())
3958 .with_mock_environment(CssMockEnvironment::windows());
3959 match config.mock_css_environment {
3960 OptionCssMockEnvironment::Some(env) => assert!(matches!(
3961 env.os,
3962 azul_css::dynamic_selector::OptionOsCondition::Some(
3963 azul_css::dynamic_selector::OsCondition::Windows
3964 )
3965 )),
3966 OptionCssMockEnvironment::None => panic!("mock env must be Some"),
3967 }
3968 }
3969
3970 #[test]
3975 fn css_mock_environment_presets_only_set_their_own_field() {
3976 use azul_css::dynamic_selector::{
3977 OptionOsCondition, OptionThemeCondition, OsCondition, ThemeCondition,
3978 };
3979
3980 for (mock, os) in [
3981 (CssMockEnvironment::linux(), OsCondition::Linux),
3982 (CssMockEnvironment::windows(), OsCondition::Windows),
3983 (CssMockEnvironment::macos(), OsCondition::MacOS),
3984 ] {
3985 assert!(matches!(mock.os, OptionOsCondition::Some(o) if o == os));
3986 assert!(matches!(mock.theme, OptionThemeCondition::None));
3988 assert!(matches!(mock.viewport_width, azul_css::OptionF32::None));
3989 }
3990
3991 assert!(matches!(
3992 CssMockEnvironment::dark_theme().theme,
3993 OptionThemeCondition::Some(ThemeCondition::Dark)
3994 ));
3995 assert!(matches!(
3996 CssMockEnvironment::light_theme().theme,
3997 OptionThemeCondition::Some(ThemeCondition::Light)
3998 ));
3999 assert!(matches!(
4000 CssMockEnvironment::dark_theme().os,
4001 OptionOsCondition::None
4002 ));
4003 }
4004
4005 #[test]
4006 fn css_mock_environment_apply_to_overrides_only_set_fields() {
4007 use azul_css::dynamic_selector::{
4008 BoolCondition, DynamicSelectorContext, OptionOsCondition, OptionThemeCondition,
4009 OsCondition, ThemeCondition,
4010 };
4011
4012 let mut ctx = DynamicSelectorContext::default();
4014 let before_os = ctx.os;
4015 let before_lang = ctx.language.clone();
4016 let before_w = ctx.viewport_width;
4017 CssMockEnvironment::default().apply_to(&mut ctx);
4018 assert_eq!(ctx.os, before_os);
4019 assert_eq!(ctx.language.as_str(), before_lang.as_str());
4020 assert_eq!(ctx.viewport_width, before_w);
4021
4022 let mock = CssMockEnvironment {
4025 os: OptionOsCondition::Some(OsCondition::Windows),
4026 theme: OptionThemeCondition::Some(ThemeCondition::Dark),
4027 language: azul_css::OptionString::Some(AzString::from_const_str("de-DE")),
4028 viewport_width: azul_css::OptionF32::Some(f32::NAN),
4029 viewport_height: azul_css::OptionF32::Some(f32::INFINITY),
4030 prefers_reduced_motion: azul_css::OptionBool::Some(true),
4031 prefers_high_contrast: azul_css::OptionBool::Some(false),
4032 ..Default::default()
4033 };
4034 let mut ctx = DynamicSelectorContext::default();
4035 mock.apply_to(&mut ctx);
4036 assert_eq!(ctx.os, OsCondition::Windows);
4037 assert_eq!(ctx.theme, ThemeCondition::Dark);
4038 assert_eq!(ctx.language.as_str(), "de-DE");
4039 assert!(ctx.viewport_width.is_nan());
4040 assert_eq!(ctx.viewport_height, f32::INFINITY);
4041 assert_eq!(ctx.prefers_reduced_motion, BoolCondition::True);
4042 assert_eq!(ctx.prefers_high_contrast, BoolCondition::False);
4043
4044 let mut ctx2 = ctx.clone();
4046 mock.apply_to(&mut ctx2);
4047 assert_eq!(ctx2.os, ctx.os);
4048 assert_eq!(ctx2.theme, ctx.theme);
4049 }
4050
4051 #[test]
4056 fn brush_dab_coverage_boundaries_and_monotonicity() {
4057 assert_eq!(brush_dab_coverage(0.0, 0.5), 1.0);
4059 assert_eq!(brush_dab_coverage(1.0, 0.5), 0.0);
4060 assert_eq!(brush_dab_coverage(-5.0, 0.5), 1.0);
4062 assert_eq!(brush_dab_coverage(2.0, 0.5), 0.0);
4063 assert_eq!(brush_dab_coverage(f32::INFINITY, 0.5), 0.0);
4064 assert_eq!(brush_dab_coverage(f32::NEG_INFINITY, 0.5), 1.0);
4065
4066 let mut prev = f32::INFINITY;
4068 for i in 0..=100 {
4069 let t = i as f32 / 100.0;
4070 let c = brush_dab_coverage(t, 0.5);
4071 assert!((0.0..=1.0).contains(&c), "coverage {c} out of range at t={t}");
4072 assert!(c <= prev + 1.0e-6, "not monotonic at t={t}");
4073 prev = c;
4074 }
4075 }
4076
4077 #[test]
4078 fn brush_dab_coverage_hardness_limits_never_divide_by_zero() {
4079 assert_eq!(brush_dab_coverage(0.5, 1.0), 1.0);
4082 assert!(brush_dab_coverage(1.0, 1.0).is_finite());
4083 assert_eq!(brush_dab_coverage(1.0, 1.0), 1.0); assert_eq!(brush_dab_coverage(2.0, 1.0), 0.0);
4085
4086 assert_eq!(brush_dab_coverage(0.5, -10.0), brush_dab_coverage(0.5, 0.0));
4088 assert_eq!(brush_dab_coverage(0.5, 10.0), brush_dab_coverage(0.5, 1.0));
4089 assert_eq!(
4090 brush_dab_coverage(0.5, f32::NEG_INFINITY),
4091 brush_dab_coverage(0.5, 0.0)
4092 );
4093 assert!(brush_dab_coverage(0.5, f32::INFINITY).is_finite());
4094 }
4095
4096 #[test]
4097 fn brush_dab_coverage_nan_propagates_without_panicking() {
4098 assert!(brush_dab_coverage(f32::NAN, 0.5).is_nan());
4101 assert!(brush_dab_coverage(0.5, f32::NAN).is_nan());
4102 assert!(brush_dab_coverage(f32::NAN, f32::NAN).is_nan());
4103 }
4104
4105 #[test]
4106 fn normalize_u16_is_monotonic_and_saturating() {
4107 assert_eq!(normalize_u16(u16::MIN), 0);
4108 assert_eq!(normalize_u16(u16::MAX), u8::MAX);
4109 let mut prev = 0u8;
4110 for i in (0..=u16::MAX).step_by(97) {
4111 let v = normalize_u16(i);
4112 assert!(v >= prev, "normalize_u16 must be monotonic ({i} -> {v})");
4113 prev = v;
4114 }
4115 }
4116
4117 #[test]
4118 fn premultiply_alpha_ignores_non_4_byte_slices() {
4119 for len in [0usize, 1, 2, 3, 5, 8] {
4121 let mut buf = vec![200u8; len];
4122 let before = buf.clone();
4123 premultiply_alpha(&mut buf);
4124 assert_eq!(buf, before, "len {len} must be left untouched");
4125 }
4126 }
4127
4128 #[test]
4129 fn premultiply_alpha_boundary_values_never_overflow() {
4130 let mut opaque = [255u8, 128, 0, 255];
4132 premultiply_alpha(&mut opaque);
4133 assert_eq!(opaque, [255, 128, 0, 255]);
4134
4135 let mut transparent = [255u8, 255, 255, 0];
4137 premultiply_alpha(&mut transparent);
4138 assert_eq!(transparent, [0, 0, 0, 0]);
4139
4140 let mut half = [255u8, 128, 0, 128];
4142 premultiply_alpha(&mut half);
4143 assert_eq!(half, [128, 64, 0, 128]);
4144
4145 let mut max = [255u8, 255, 255, 255];
4147 premultiply_alpha(&mut max);
4148 assert_eq!(max, [255, 255, 255, 255]);
4149 }
4150
4151 #[test]
4152 fn au_from_px_saturates_at_limits_and_nan() {
4153 assert_eq!(Au::from_px(0.0).0, 0);
4154 assert_eq!(Au::from_px(-0.0).0, 0);
4155 assert_eq!(Au::from_px(1.0).0, AU_PER_PX);
4156 assert_eq!(Au::from_px(-1.0).0, -AU_PER_PX);
4157 assert_eq!(Au::from_px(f32::NAN).0, 0);
4159 assert_eq!(Au::from_px(f32::INFINITY).0, MAX_AU);
4161 assert_eq!(Au::from_px(f32::NEG_INFINITY).0, MIN_AU);
4162 assert_eq!(Au::from_px(f32::MAX).0, MAX_AU);
4163 assert_eq!(Au::from_px(f32::MIN).0, MIN_AU);
4164 for px in [-1.0e9_f32, -1.0, 0.5, 16.0, 1.0e9] {
4166 let au = Au::from_px(px).0;
4167 assert!((MIN_AU..=MAX_AU).contains(&au), "{px} -> {au} escaped the clamp");
4168 }
4169 }
4170
4171 #[test]
4172 fn au_px_round_trip_is_stable() {
4173 for px in [0.0_f32, 0.5, 1.0, 12.0, 16.0, 72.5, -3.25, 1000.0] {
4175 let back = Au::from_px(px).into_px();
4176 assert!(
4177 (back - px).abs() <= 1.0 / AU_PER_PX as f32,
4178 "{px} round-tripped to {back}"
4179 );
4180 }
4181 assert_eq!(Au::from_px(16.0).into_px(), 16.0);
4183 assert!(Au(MAX_AU).into_px().is_finite());
4185 assert!(Au(MIN_AU).into_px().is_finite());
4186 assert!(Au(i32::MIN).into_px().is_finite());
4187 assert!(Au(i32::MAX).into_px().is_finite());
4188 }
4189
4190 #[test]
4191 fn font_size_to_au_zero_negative_and_typical() {
4192 use azul_css::props::basic::PixelValue;
4193 let au = |px: isize| {
4194 font_size_to_au(StyleFontSize {
4195 inner: PixelValue::const_px(px),
4196 })
4197 .0
4198 };
4199 assert_eq!(au(0), 0);
4200 assert_eq!(au(16), 16 * AU_PER_PX);
4201 assert_eq!(au(-10), -10 * AU_PER_PX);
4202 assert!((MIN_AU..=MAX_AU).contains(&au(1_000_000)));
4204 assert!((MIN_AU..=MAX_AU).contains(&au(-1_000_000)));
4205 }
4206
4207 #[test]
4212 fn epoch_new_from_and_into_u32() {
4213 assert_eq!(Epoch::new().into_u32(), 0);
4214 assert_eq!(Epoch::default().into_u32(), 0);
4215 assert_eq!(Epoch::from(0).into_u32(), 0);
4216 assert_eq!(Epoch::from(1).into_u32(), 1);
4217 assert_eq!(Epoch::from(u32::MAX).into_u32(), u32::MAX);
4218 assert_eq!(Epoch::from(u32::MAX - 1).into_u32(), u32::MAX - 1);
4219 }
4220
4221 #[test]
4222 fn epoch_increment_wraps_at_max_minus_one_and_never_reaches_max() {
4223 let mut e = Epoch::new();
4224 e.increment();
4225 assert_eq!(e.into_u32(), 1);
4226
4227 let mut e = Epoch::from(u32::MAX - 1);
4229 e.increment();
4230 assert_eq!(e.into_u32(), 0, "MAX-1 must wrap to 0, never to u32::MAX");
4231
4232 let mut e = Epoch::from(u32::MAX);
4235 e.increment();
4236 assert_eq!(e.into_u32(), u32::MAX);
4237
4238 let mut e = Epoch::from(u32::MAX - 3);
4240 for _ in 0..8 {
4241 e.increment();
4242 assert_ne!(e.into_u32(), u32::MAX);
4243 }
4244 }
4245
4246 #[test]
4247 fn epoch_display_is_non_empty_for_edge_values() {
4248 assert_eq!(alloc::format!("{}", Epoch::new()), "0");
4249 assert_eq!(alloc::format!("{}", Epoch::from(42)), "42");
4250 assert_eq!(
4251 alloc::format!("{}", Epoch::from(u32::MAX)),
4252 alloc::format!("{}", u32::MAX)
4253 );
4254 assert!(!alloc::format!("{:?}", Epoch::default()).is_empty());
4255 }
4256
4257 #[test]
4258 fn id_namespace_display_and_debug_are_well_formed() {
4259 assert_eq!(alloc::format!("{}", IdNamespace(0)), "IdNamespace(0)");
4260 assert_eq!(
4261 alloc::format!("{}", IdNamespace(u32::MAX)),
4262 alloc::format!("IdNamespace({})", u32::MAX)
4263 );
4264 assert_eq!(
4266 alloc::format!("{:?}", IdNamespace(7)),
4267 alloc::format!("{}", IdNamespace(7))
4268 );
4269 }
4270
4271 #[test]
4276 fn unique_keys_are_strictly_increasing_and_keep_their_namespace() {
4277 let ns = IdNamespace(u32::MAX);
4278
4279 let a = ImageKey::unique(ns);
4280 let b = ImageKey::unique(ns);
4281 assert_eq!(a.namespace, ns);
4282 assert!(b.key > a.key, "ImageKey counter must strictly increase");
4283 assert_eq!(ImageKey::DUMMY.key, 0);
4285 assert_ne!(a, ImageKey::DUMMY);
4286
4287 let a = FontKey::unique(ns);
4288 let b = FontKey::unique(ns);
4289 assert_eq!(a.namespace, ns);
4290 assert!(b.key > a.key);
4291
4292 let a = FontInstanceKey::unique(IdNamespace(0));
4293 let b = FontInstanceKey::unique(IdNamespace(0));
4294 assert_eq!(a.namespace, IdNamespace(0));
4295 assert!(b.key > a.key);
4296 }
4297
4298 #[test]
4299 fn image_ref_id_counter_is_monotonic_and_never_zero() {
4300 let a = next_image_ref_id();
4302 let b = next_image_ref_id();
4303 assert!(a > 0 && b > a);
4304 }
4305
4306 #[test]
4307 fn image_ref_hash_conversions_are_lossless_and_agree() {
4308 let img = ImageRef::null_image(1, 1, RawImageFormat::RGBA8, Vec::new());
4309 let hash = img.get_hash();
4310 assert_eq!(hash, image_ref_get_hash(&img));
4311
4312 let key = image_ref_hash_to_image_key(hash, IdNamespace(9));
4313 assert_eq!(key.namespace, IdNamespace(9));
4314 assert_eq!(key.key, hash.inner, "the u64 id must survive verbatim");
4315
4316 let ext = image_ref_hash_to_external_image_id(hash);
4317 assert_eq!(ext.inner, hash.inner);
4318
4319 for inner in [0u64, 1, u64::MAX, u64::MAX - 1] {
4321 let h = ImageRefHash { inner };
4322 assert_eq!(image_ref_hash_to_image_key(h, IdNamespace(0)).key, inner);
4323 assert_eq!(image_ref_hash_to_external_image_id(h).inner, inner);
4324 }
4325 }
4326
4327 #[test]
4328 fn texture_external_image_id_is_deterministic_and_collision_free() {
4329 let id = |d: usize, n: usize| texture_external_image_id(DomId { inner: d }, NodeId::new(n));
4330
4331 assert_eq!(id(3, 7), id(3, 7));
4333 assert_eq!(id(0, 0).inner, 0);
4334 assert_eq!(id(1, 2).inner, (1u64 << 32) | 2);
4336 assert_ne!(id(0, 1), id(1, 0));
4338 assert_eq!(id(0, u32::MAX as usize).inner, u64::from(u32::MAX));
4340 assert_eq!(
4341 id(u32::MAX as usize, 0).inner,
4342 u64::from(u32::MAX) << 32
4343 );
4344 }
4345
4346 #[test]
4351 fn raw_image_data_typed_getters_only_match_their_own_variant() {
4352 let u8v = RawImageData::U8(vec![1u8, 2].into());
4353 let u16v = RawImageData::U16(vec![1u16, 2].into());
4354 let f32v = RawImageData::F32(vec![1.0f32, 2.0].into());
4355
4356 assert_eq!(u8v.get_u8_vec_ref().map(|v| v.len()), Some(2));
4357 assert!(u8v.get_u16_vec_ref().is_none());
4358 assert!(u8v.get_f32_vec_ref().is_none());
4359
4360 assert!(u16v.get_u8_vec_ref().is_none());
4361 assert_eq!(u16v.get_u16_vec_ref().map(|v| v.len()), Some(2));
4362 assert!(u16v.get_f32_vec_ref().is_none());
4363
4364 assert!(f32v.get_u8_vec_ref().is_none());
4365 assert!(f32v.get_u16_vec_ref().is_none());
4366 assert_eq!(f32v.get_f32_vec_ref().map(|v| v.len()), Some(2));
4367
4368 let empty = RawImageData::U8(U8Vec::from_vec(Vec::new()));
4370 assert_eq!(empty.get_u8_vec_ref().map(|v| v.len()), Some(0));
4371
4372 assert!(RawImageData::U8(vec![9u8].into()).get_u8_vec().is_some());
4374 assert!(RawImageData::U16(vec![9u16].into()).get_u8_vec().is_none());
4375 assert!(RawImageData::U16(vec![9u16].into()).get_u16_vec().is_some());
4376 assert!(RawImageData::F32(vec![9.0f32].into()).get_u16_vec().is_none());
4377 }
4378
4379 #[test]
4384 fn load_fns_reject_wrong_payload_type() {
4385 let u16_1px = || RawImageData::U16(vec![0u16; 4].into());
4388 let f32_1px = || RawImageData::F32(vec![0.0f32; 4].into());
4389 let u8_1px = || RawImageData::U8(vec![0u8; 4].into());
4390
4391 assert!(RawImage::load_r8(u16_1px(), 4).is_none());
4392 assert!(RawImage::load_rg8(f32_1px(), 2, true).is_none());
4393 assert!(RawImage::load_rgb8(u16_1px(), 1).is_none());
4394 assert!(RawImage::load_rgba8(f32_1px(), 1, true).is_none());
4395 assert!(RawImage::load_r16(u8_1px(), 4).is_none());
4396 assert!(RawImage::load_rg16(f32_1px(), 2).is_none());
4397 assert!(RawImage::load_rgb16(u8_1px(), 1).is_none());
4398 assert!(RawImage::load_rgba16(u8_1px(), 1, true).is_none());
4399 assert!(RawImage::load_bgr8(u16_1px(), 1).is_none());
4400 assert!(RawImage::load_bgra8(u16_1px(), 1, true).is_none());
4401 assert!(RawImage::load_rgbf32(u8_1px(), 1).is_none());
4402 assert!(RawImage::load_rgbaf32(u16_1px(), 1, true).is_none());
4403 }
4404
4405 #[test]
4406 fn load_fns_reject_every_wrong_length() {
4407 assert!(RawImage::load_r8(RawImageData::U8(vec![0u8; 3].into()), 4).is_none());
4409 assert!(RawImage::load_r8(RawImageData::U8(vec![0u8; 5].into()), 4).is_none());
4410 assert!(RawImage::load_rg8(RawImageData::U8(vec![0u8; 3].into()), 2, true).is_none());
4411 assert!(RawImage::load_rg8(RawImageData::U8(vec![0u8; 5].into()), 2, true).is_none());
4412 assert!(RawImage::load_rgb8(RawImageData::U8(vec![0u8; 5].into()), 2).is_none());
4413 assert!(RawImage::load_rgb8(RawImageData::U8(vec![0u8; 7].into()), 2).is_none());
4414 assert!(RawImage::load_rgba8(RawImageData::U8(vec![0u8; 7].into()), 2, true).is_none());
4415 assert!(RawImage::load_rgba8(RawImageData::U8(vec![0u8; 9].into()), 2, false).is_none());
4416 assert!(RawImage::load_r16(RawImageData::U16(vec![0u16; 3].into()), 4).is_none());
4417 assert!(RawImage::load_rg16(RawImageData::U16(vec![0u16; 3].into()), 2).is_none());
4418 assert!(RawImage::load_rgb16(RawImageData::U16(vec![0u16; 5].into()), 2).is_none());
4419 assert!(RawImage::load_rgba16(RawImageData::U16(vec![0u16; 7].into()), 2, true).is_none());
4420 assert!(RawImage::load_bgr8(RawImageData::U8(vec![0u8; 5].into()), 2).is_none());
4421 assert!(RawImage::load_bgra8(RawImageData::U8(vec![0u8; 7].into()), 2, false).is_none());
4422 assert!(RawImage::load_rgbf32(RawImageData::F32(vec![0.0f32; 5].into()), 2).is_none());
4423 assert!(
4424 RawImage::load_rgbaf32(RawImageData::F32(vec![0.0f32; 7].into()), 2, true).is_none()
4425 );
4426 }
4427
4428 #[test]
4429 fn load_fns_accept_zero_pixels() {
4430 let empty_u8 = || RawImageData::U8(U8Vec::from_vec(Vec::new()));
4432 let empty_u16 = || RawImageData::U16(U16Vec::from_vec(Vec::new()));
4433 let empty_f32 = || RawImageData::F32(F32Vec::from_vec(Vec::new()));
4434
4435 assert_eq!(RawImage::load_r8(empty_u8(), 0).map(|(b, o)| (b.len(), o)), Some((0, false)));
4436 assert_eq!(RawImage::load_rg8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4437 assert_eq!(RawImage::load_rgb8(empty_u8(), 0).map(|(b, _)| b.len()), Some(0));
4438 assert_eq!(RawImage::load_rgba8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4439 assert_eq!(RawImage::load_r16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4440 assert_eq!(RawImage::load_rg16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4441 assert_eq!(RawImage::load_rgb16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4442 assert_eq!(RawImage::load_rgba16(empty_u16(), 0, true).map(|(b, _)| b.len()), Some(0));
4443 assert_eq!(RawImage::load_bgr8(empty_u8(), 0).map(|(b, _)| b.len()), Some(0));
4444 assert_eq!(RawImage::load_bgra8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4445 assert_eq!(RawImage::load_rgbf32(empty_f32(), 0).map(|(b, _)| b.len()), Some(0));
4446 assert_eq!(RawImage::load_rgbaf32(empty_f32(), 0, true).map(|(b, _)| b.len()), Some(0));
4447 }
4448
4449 #[test]
4450 fn load_r8_passes_data_through_and_is_never_opaque() {
4451 let (bytes, is_opaque) =
4453 RawImage::load_r8(RawImageData::U8(vec![0u8, 128, 255, 1].into()), 4)
4454 .expect("exact length");
4455 assert_eq!(bytes.as_ref(), &[0, 128, 255, 1]);
4456 assert!(!is_opaque, "R8 is documented as never opaque");
4457 }
4458
4459 #[test]
4460 fn load_rgb8_and_bgr8_swizzle_to_bgra_opaque() {
4461 let (bytes, is_opaque) =
4463 RawImage::load_rgb8(RawImageData::U8(vec![1u8, 2, 3].into()), 1).expect("1 px");
4464 assert_eq!(bytes.as_ref(), &[3, 2, 1, 255]);
4465 assert!(is_opaque);
4466
4467 let (bytes, is_opaque) =
4469 RawImage::load_bgr8(RawImageData::U8(vec![1u8, 2, 3].into()), 1).expect("1 px");
4470 assert_eq!(bytes.as_ref(), &[1, 2, 3, 255]);
4471 assert!(is_opaque);
4472 }
4473
4474 #[test]
4475 fn load_rgba8_swizzles_and_detects_transparency() {
4476 let (bytes, is_opaque) =
4478 RawImage::load_rgba8(RawImageData::U8(vec![10u8, 20, 30, 255].into()), 1, true)
4479 .expect("1 px");
4480 assert_eq!(bytes.as_ref(), &[30, 20, 10, 255]);
4481 assert!(is_opaque);
4482
4483 let (_, is_opaque) =
4485 RawImage::load_rgba8(RawImageData::U8(vec![0u8, 0, 0, 254].into()), 1, true)
4486 .expect("1 px");
4487 assert!(!is_opaque);
4488
4489 let (bytes, is_opaque) =
4491 RawImage::load_rgba8(RawImageData::U8(vec![10u8, 20, 30, 128].into()), 1, false)
4492 .expect("1 px");
4493 assert_eq!(bytes.as_ref(), &[15, 10, 5, 128]);
4494 assert!(!is_opaque);
4495
4496 let (bytes, _) =
4498 RawImage::load_rgba8(RawImageData::U8(vec![255u8, 255, 255, 0].into()), 1, false)
4499 .expect("1 px");
4500 assert_eq!(bytes.as_ref(), &[0, 0, 0, 0]);
4501 }
4502
4503 #[test]
4504 fn load_rg8_expands_grey_to_bgra() {
4505 let (bytes, is_opaque) =
4507 RawImage::load_rg8(RawImageData::U8(vec![100u8, 255].into()), 1, true).expect("1 px");
4508 assert_eq!(bytes.as_ref(), &[100, 100, 100, 255]);
4509 assert!(is_opaque);
4510
4511 let (bytes, is_opaque) =
4512 RawImage::load_rg8(RawImageData::U8(vec![100u8, 128].into()), 1, false).expect("1 px");
4513 assert_eq!(bytes.as_ref(), &[50, 50, 50, 128]);
4514 assert!(!is_opaque);
4515 }
4516
4517 #[test]
4518 fn load_16_bit_formats_normalize_to_8_bit() {
4519 let (bytes, is_opaque) =
4521 RawImage::load_r16(RawImageData::U16(vec![u16::MAX].into()), 1).expect("1 px");
4522 assert_eq!(bytes.as_ref(), &[255, 255, 255, 255]);
4523 assert!(is_opaque);
4524
4525 let (bytes, is_opaque) =
4526 RawImage::load_rg16(RawImageData::U16(vec![0u16, u16::MAX].into()), 1).expect("1 px");
4527 assert_eq!(bytes.as_ref(), &[0, 0, 0, 255]);
4528 assert!(is_opaque);
4529
4530 let (bytes, _) = RawImage::load_rgb16(
4532 RawImageData::U16(vec![u16::MAX, 0, 0].into()),
4533 1,
4534 )
4535 .expect("1 px");
4536 assert_eq!(bytes.as_ref(), &[0, 0, 255, 255]);
4537
4538 let (bytes, is_opaque) = RawImage::load_rgba16(
4540 RawImageData::U16(vec![u16::MAX, u16::MAX, u16::MAX, 0].into()),
4541 1,
4542 false,
4543 )
4544 .expect("1 px");
4545 assert_eq!(bytes.as_ref(), &[0, 0, 0, 0]);
4546 assert!(!is_opaque);
4547 }
4548
4549 #[test]
4550 fn load_f32_formats_saturate_on_out_of_range_nan_and_inf() {
4551 let (bytes, is_opaque) = RawImage::load_rgbf32(
4554 RawImageData::F32(vec![2.0f32, -1.0, f32::NAN].into()),
4555 1,
4556 )
4557 .expect("1 px");
4558 assert_eq!(bytes.as_ref(), &[0, 0, 255, 255], "b=NaN->0, g=-1->0, r=2.0->255");
4559 assert!(is_opaque);
4560
4561 let (bytes, is_opaque) = RawImage::load_rgbaf32(
4562 RawImageData::F32(vec![f32::INFINITY, f32::NEG_INFINITY, 0.5, 1.0].into()),
4563 1,
4564 true,
4565 )
4566 .expect("1 px");
4567 assert_eq!(bytes.as_ref(), &[127, 0, 255, 255]);
4568 assert!(is_opaque);
4569
4570 let (_, is_opaque) = RawImage::load_rgbaf32(
4572 RawImageData::F32(vec![1.0f32, 1.0, 1.0, f32::NAN].into()),
4573 1,
4574 true,
4575 )
4576 .expect("1 px");
4577 assert!(!is_opaque);
4578 }
4579
4580 #[test]
4585 fn raw_image_null_image_encodes_to_an_empty_bgra8_descriptor() {
4586 let null = RawImage::null_image();
4587 assert_eq!(null.width, 0);
4588 assert_eq!(null.height, 0);
4589 assert_eq!(null.data_format, RawImageFormat::BGRA8);
4590 assert!(null.premultiplied_alpha);
4591
4592 let (data, descriptor) = null
4593 .into_loaded_image_source()
4594 .expect("a 0x0 image is still a valid (empty) source");
4595 assert_eq!(descriptor.width, 0);
4596 assert_eq!(descriptor.height, 0);
4597 assert_eq!(descriptor.format, RawImageFormat::BGRA8);
4598 assert_eq!(descriptor.offset, 0);
4599 match data {
4600 ImageData::Raw(bytes) => assert!(bytes.is_empty()),
4601 ImageData::External(_) => panic!("a RawImage must never encode to External"),
4602 }
4603 }
4604
4605 #[test]
4606 fn raw_image_allocate_mask_zero_and_negative_sizes() {
4607 let mask = RawImage::allocate_mask(LayoutSize::zero());
4608 assert_eq!(mask.data_format, RawImageFormat::R8);
4609 assert_eq!(mask.width, 0);
4610 assert_eq!(mask.height, 0);
4611 assert_eq!(mask.pixels.get_u8_vec_ref().map(|v| v.len()), Some(0));
4612
4613 let mask = RawImage::allocate_mask(LayoutSize::new(4, 4));
4614 assert_eq!(mask.pixels.get_u8_vec_ref().map(|v| v.len()), Some(16));
4615 assert!(mask
4616 .pixels
4617 .get_u8_vec_ref()
4618 .expect("u8")
4619 .as_ref()
4620 .iter()
4621 .all(|b| *b == 0));
4622
4623 let mask = RawImage::allocate_mask(LayoutSize::new(-4, 4));
4628 assert_eq!(
4629 mask.pixels.get_u8_vec_ref().map(|v| v.len()),
4630 Some(0),
4631 "a negative extent must never allocate"
4632 );
4633 assert!(mask.width > 1_000_000, "negative width wraps via `as usize`");
4634 }
4635
4636 #[test]
4637 fn raw_image_mask_round_trips_as_r8() {
4638 let mask = RawImage::allocate_mask(LayoutSize::new(2, 2));
4641 let (data, descriptor) = mask.into_loaded_image_source().expect("consistent mask");
4642 assert_eq!(descriptor.format, RawImageFormat::R8);
4643 assert_eq!((descriptor.width, descriptor.height), (2, 2));
4644 assert!(!descriptor.flags.is_opaque, "R8 is never opaque");
4645 match data {
4646 ImageData::Raw(bytes) => assert_eq!(bytes.len(), 4),
4647 ImageData::External(_) => panic!("expected raw bytes"),
4648 }
4649 }
4650
4651 #[test]
4652 fn raw_image_rgba8_encode_decode_round_trip() {
4653 let raw = RawImage {
4656 pixels: RawImageData::U8(vec![10u8, 20, 30, 255].into()),
4657 width: 1,
4658 height: 1,
4659 premultiplied_alpha: true,
4660 data_format: RawImageFormat::RGBA8,
4661 tag: Vec::new().into(),
4662 };
4663 let img = ImageRef::new_rawimage(raw).expect("1x1 RGBA8 with 4 bytes is valid");
4664
4665 assert!(img.is_raw_image());
4666 assert!(!img.is_null_image());
4667 assert!(!img.is_gl_texture());
4668 assert!(!img.is_callback());
4669 assert_eq!(img.get_size(), LogicalSize::new(1.0, 1.0));
4670 assert_eq!(img.get_bytes(), Some(&[30u8, 20, 10, 255][..]));
4671 assert!(!img.get_bytes_ptr().is_null());
4672
4673 let decoded = img.get_rawimage().expect("raw image round-trips");
4674 assert_eq!(decoded.width, 1);
4675 assert_eq!(decoded.height, 1);
4676 assert_eq!(decoded.data_format, RawImageFormat::BGRA8);
4677 assert!(decoded.premultiplied_alpha);
4678 assert_eq!(
4679 decoded.pixels.get_u8_vec_ref().map(|v| v.as_ref().to_vec()),
4680 Some(vec![30, 20, 10, 255])
4681 );
4682 }
4683
4684 #[test]
4685 fn image_ref_new_rawimage_rejects_dimension_mismatch() {
4686 let too_small = RawImage {
4688 pixels: RawImageData::U8(vec![0u8; 4].into()),
4689 width: 2,
4690 height: 2,
4691 premultiplied_alpha: true,
4692 data_format: RawImageFormat::RGBA8,
4693 tag: Vec::new().into(),
4694 };
4695 assert!(ImageRef::new_rawimage(too_small).is_none());
4696
4697 let too_big = RawImage {
4699 pixels: RawImageData::U8(vec![0u8; 64].into()),
4700 width: 2,
4701 height: 2,
4702 premultiplied_alpha: true,
4703 data_format: RawImageFormat::RGBA8,
4704 tag: Vec::new().into(),
4705 };
4706 assert!(ImageRef::new_rawimage(too_big).is_none());
4707
4708 let wrong_type = RawImage {
4710 pixels: RawImageData::U16(vec![0u16; 16].into()),
4711 width: 2,
4712 height: 2,
4713 premultiplied_alpha: true,
4714 data_format: RawImageFormat::RGBA8,
4715 tag: Vec::new().into(),
4716 };
4717 assert!(ImageRef::new_rawimage(wrong_type).is_none());
4718 }
4719
4720 #[test]
4725 fn image_ref_null_image_predicates_and_accessors() {
4726 let img = ImageRef::null_image(0, 0, RawImageFormat::BGRA8, Vec::new());
4727 assert!(img.is_null_image());
4728 assert!(!img.is_raw_image());
4729 assert!(!img.is_gl_texture());
4730 assert!(!img.is_callback());
4731 assert_eq!(img.get_size(), LogicalSize::new(0.0, 0.0));
4732 assert!(img.get_bytes().is_none());
4733 assert!(img.get_rawimage().is_none());
4734 assert!(img.get_bytes_ptr().is_null());
4735 assert!(img.get_image_callback().is_none());
4736 assert!(matches!(img.get_data(), DecodedImage::NullImage { .. }));
4737 }
4738
4739 #[test]
4740 fn image_ref_null_image_at_usize_max_reports_a_finite_size() {
4741 let img = ImageRef::null_image(usize::MAX, usize::MAX, RawImageFormat::R8, Vec::new());
4743 let size = img.get_size();
4744 assert!(size.width.is_finite() && size.height.is_finite());
4745 assert!(size.width > 0.0 && size.height > 0.0);
4746 assert!(img.is_null_image());
4747
4748 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, vec![9u8; 10_000]);
4750 match img.get_data() {
4751 DecodedImage::NullImage { tag, .. } => assert_eq!(tag.len(), 10_000),
4752 _ => panic!("expected NullImage"),
4753 }
4754 }
4755
4756 #[test]
4757 fn image_ref_hash_identity_rules() {
4758 let a = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4759 let b = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4760 assert_ne!(a.get_hash(), b.get_hash());
4762 assert_ne!(a, b);
4763
4764 let a2 = a.clone();
4766 assert_eq!(a.get_hash(), a2.get_hash());
4767 assert_eq!(a, a2);
4768
4769 let deep = a.deep_copy();
4771 assert_ne!(a.get_hash(), deep.get_hash());
4772 assert!(deep.is_null_image());
4773 assert_eq!(deep.get_size(), a.get_size());
4774 }
4775
4776 #[test]
4777 fn image_ref_callback_accessors() {
4778 let mut img = ImageRef::callback(0usize, RefAny::new(123u32));
4781 assert!(img.is_callback());
4782 assert!(!img.is_null_image());
4783 assert!(!img.is_raw_image());
4784 assert_eq!(img.get_size(), LogicalSize::new(0.0, 0.0));
4786 assert!(img.get_bytes().is_none());
4787 assert!(img.get_bytes_ptr().is_null());
4788 assert!(img.get_rawimage().is_none());
4789
4790 assert!(img.get_image_callback().is_some());
4792 assert!(img.get_image_callback_mut().is_some());
4793
4794 let clone = img.clone();
4797 assert!(img.get_image_callback().is_none());
4798 assert!(img.get_image_callback_mut().is_none());
4799 drop(clone);
4800 assert!(img.get_image_callback().is_some());
4801 }
4802
4803 #[test]
4804 fn image_ref_deep_copy_of_a_callback_keeps_it_a_callback() {
4805 let img = ImageRef::callback(0usize, RefAny::new(1u8));
4806 let deep = img.deep_copy();
4807 assert!(deep.is_callback());
4808 assert_ne!(img.get_hash(), deep.get_hash());
4809 }
4810
4811 #[test]
4812 fn image_ref_into_inner_only_when_sole_owner() {
4813 let img = ImageRef::null_image(2, 2, RawImageFormat::RGBA8, vec![1, 2, 3]);
4814 let clone = img.clone();
4815 assert!(clone.into_inner().is_none(), "shared -> must refuse");
4816
4817 let inner = img.into_inner().expect("sole owner -> takes ownership");
4818 match inner {
4819 DecodedImage::NullImage {
4820 width,
4821 height,
4822 format,
4823 tag,
4824 } => {
4825 assert_eq!((width, height), (2, 2));
4826 assert_eq!(format, RawImageFormat::RGBA8);
4827 assert_eq!(tag, vec![1, 2, 3]);
4828 }
4829 _ => panic!("expected NullImage"),
4830 }
4831 }
4832
4833 #[test]
4838 fn image_cache_add_get_delete_round_trip() {
4839 let mut cache = ImageCache::new();
4840 let key = AzString::from_const_str("my_image");
4841 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4842 let hash = img.get_hash();
4843
4844 assert!(cache.get_css_image_id(&key).is_none());
4845 cache.add_css_image_id(key.clone(), img);
4846 assert_eq!(cache.get_css_image_id(&key).map(ImageRef::get_hash), Some(hash));
4847
4848 let img2 = ImageRef::null_image(2, 2, RawImageFormat::R8, Vec::new());
4850 let hash2 = img2.get_hash();
4851 cache.add_css_image_id(key.clone(), img2);
4852 assert_eq!(cache.image_id_map.len(), 1);
4853 assert_eq!(cache.get_css_image_id(&key).map(ImageRef::get_hash), Some(hash2));
4854
4855 cache.delete_css_image_id(&key);
4856 assert!(cache.get_css_image_id(&key).is_none());
4857 assert!(cache.image_id_map.is_empty());
4858 cache.delete_css_image_id(&key);
4860 cache.delete_css_image_id(&AzString::from_const_str("never-existed"));
4861 }
4862
4863 #[test]
4864 fn image_cache_handles_empty_and_unicode_keys() {
4865 let mut cache = ImageCache::new();
4866 let empty = AzString::from_const_str("");
4867 let unicode = AzString::from(String::from("\u{1F600}\u{0301}"));
4868 let long = AzString::from("k".repeat(100_000));
4869
4870 cache.add_css_image_id(
4871 empty.clone(),
4872 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4873 );
4874 cache.add_css_image_id(
4875 unicode.clone(),
4876 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4877 );
4878 cache.add_css_image_id(
4879 long.clone(),
4880 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4881 );
4882
4883 assert_eq!(cache.image_id_map.len(), 3);
4884 assert!(cache.get_css_image_id(&empty).is_some());
4885 assert!(cache.get_css_image_id(&unicode).is_some());
4886 assert!(cache.get_css_image_id(&long).is_some());
4887 assert!(cache
4889 .get_css_image_id(&AzString::from_const_str("\u{1F600}"))
4890 .is_none());
4891 }
4892
4893 #[test]
4898 fn renderer_resources_lookups_on_an_empty_registry_are_none() {
4899 let rr = RendererResources::default();
4900 let ns = IdNamespace(1);
4901 assert!(rr
4902 .get_renderable_font_data(&FontInstanceKey::unique(ns))
4903 .is_none());
4904 let families = StyleFontFamiliesHash::new(&[]);
4905 assert!(rr
4906 .get_font_instance_key(&families, Au(0), DpiScaleFactor::new(1.0))
4907 .is_none());
4908 assert!(rr
4909 .get_font_instance_key(&families, Au(MAX_AU), DpiScaleFactor::new(f32::NAN))
4910 .is_none());
4911 assert!(rr.get_image(&ImageRefHash { inner: 0 }).is_none());
4912 assert!(rr.get_font_key(&StyleFontFamilyHash::new(&StyleFontFamily::System(
4913 AzString::from_const_str("Arial")
4914 ))).is_none());
4915 }
4916
4917 #[test]
4918 fn renderer_resources_gc_helper_is_a_noop_on_empty_maps() {
4919 let mut rr = RendererResources::default();
4921 rr.remove_font_families_with_zero_references();
4922 assert!(rr.font_id_map.is_empty());
4923 assert!(rr.font_families_map.is_empty());
4924
4925 let family = StyleFontFamily::System(AzString::from_const_str("Arial"));
4928 let family_hash = StyleFontFamilyHash::new(&family);
4929 let families_hash = StyleFontFamiliesHash::new(core::slice::from_ref(&family));
4930 rr.font_id_map.insert(family_hash, FontKey::unique(IdNamespace(1)));
4931 rr.font_families_map.insert(families_hash, family_hash);
4932 rr.remove_font_families_with_zero_references();
4933 assert!(rr.font_id_map.is_empty(), "dangling font key must be pruned");
4934 assert!(rr.font_families_map.is_empty());
4935 }
4936
4937 #[test]
4938 fn get_font_instance_key_for_text_is_none_on_empty_resources_for_all_sane_sizes() {
4939 let rr = RendererResources::default();
4942 let cache = CssPropertyCache::default();
4943 let node = NodeData::default();
4944 let node_id = NodeId::new(0);
4945 let state = StyledNodeState::default();
4946
4947 for size in [0.0_f32, -0.0, 1.0, -12.0, f32::NAN, 1.0e6, -1.0e6] {
4948 for dpi in [1.0_f32, 0.0, -1.0, f32::NAN, f32::INFINITY] {
4949 assert!(
4950 rr.get_font_instance_key_for_text(size, &cache, &node, &node_id, &state, dpi)
4951 .is_none(),
4952 "size={size} dpi={dpi} must miss cleanly"
4953 );
4954 }
4955 }
4956 }
4957
4958 #[test]
4959 fn bug_get_font_instance_key_for_text_overflow_panics_on_infinite_font_size() {
4960 let rr = RendererResources::default();
4965 let cache = CssPropertyCache::default();
4966 let node = NodeData::default();
4967 let node_id = NodeId::new(0);
4968 let state = StyledNodeState::default();
4969 assert!(rr
4970 .get_font_instance_key_for_text(
4971 f32::INFINITY,
4972 &cache,
4973 &node,
4974 &node_id,
4975 &state,
4976 1.0
4977 )
4978 .is_none());
4979 }
4980
4981 #[test]
4986 fn font_ref_get_hash_is_stable_per_font_and_distinct_across_fonts() {
4987 let a = dummy_font_ref();
4988 let b = dummy_font_ref();
4989 assert_eq!(font_ref_get_hash(&a), font_ref_get_hash(&a));
4990 assert_eq!(font_ref_get_hash(&a), font_ref_get_hash(&a.clone()));
4991 assert_ne!(
4992 font_ref_get_hash(&a),
4993 font_ref_get_hash(&b),
4994 "two distinct FontRefs must not share a hash"
4995 );
4996 }
4997
4998 #[test]
4999 fn build_add_font_resource_updates_on_empty_input_is_empty() {
5000 let mut rr = RendererResources::default();
5001 let fonts = OrderedMap::new();
5002 let updates = build_add_font_resource_updates(
5003 &mut rr,
5004 DpiScaleFactor::new(1.0),
5005 &FcFontCache::default(),
5006 IdNamespace(1),
5007 &fonts,
5008 load_font_none,
5009 parse_font_none,
5010 );
5011 assert!(updates.is_empty());
5012 assert!(rr.font_id_map.is_empty());
5013 }
5014
5015 #[test]
5016 fn build_add_font_resource_updates_skips_unloadable_fonts() {
5017 let mut rr = RendererResources::default();
5020 let mut fonts = OrderedMap::new();
5021 let mut sizes = FastBTreeSet::new();
5022 sizes.insert(Au::from_px(16.0));
5023 fonts.insert(
5024 ImmediateFontId::Unresolved(StyleFontFamilyVec::from_vec(vec![
5025 StyleFontFamily::System(AzString::from_const_str("DoesNotExist")),
5026 ])),
5027 sizes,
5028 );
5029
5030 let updates = build_add_font_resource_updates(
5031 &mut rr,
5032 DpiScaleFactor::new(1.0),
5033 &FcFontCache::default(),
5034 IdNamespace(1),
5035 &fonts,
5036 load_font_none,
5037 parse_font_none,
5038 );
5039 assert!(updates.is_empty(), "an unloadable font must add no resources");
5040 assert!(rr.font_id_map.is_empty());
5041 assert!(rr.font_families_map.is_empty());
5042 }
5043
5044 #[test]
5045 fn build_add_font_resource_updates_registers_a_font_and_deduplicates_sizes() {
5046 let mut rr = RendererResources::default();
5049 let font = dummy_font_ref();
5050 let family = StyleFontFamily::Ref(font.clone());
5051 let dpi = DpiScaleFactor::new(1.0);
5052
5053 let mut sizes = FastBTreeSet::new();
5054 sizes.insert(Au::from_px(16.0));
5055 sizes.insert(Au::from_px(24.0));
5056 sizes.insert(Au::from_px(16.0)); assert_eq!(sizes.len(), 2);
5058
5059 let mut fonts = OrderedMap::new();
5060 fonts.insert(
5061 ImmediateFontId::Unresolved(StyleFontFamilyVec::from_vec(vec![family.clone()])),
5062 sizes,
5063 );
5064
5065 let updates = build_add_font_resource_updates(
5066 &mut rr,
5067 dpi,
5068 &FcFontCache::default(),
5069 IdNamespace(1),
5070 &fonts,
5071 load_font_none,
5072 parse_font_none,
5073 );
5074 assert_eq!(updates.len(), 3);
5076 assert_eq!(
5077 updates
5078 .iter()
5079 .filter(|(_, m)| matches!(m, AddFontMsg::Font(..)))
5080 .count(),
5081 1
5082 );
5083 assert_eq!(
5084 updates
5085 .iter()
5086 .filter(|(_, m)| matches!(m, AddFontMsg::Instance(..)))
5087 .count(),
5088 2
5089 );
5090 assert_eq!(rr.font_id_map.len(), 1);
5091 assert_eq!(rr.font_families_map.len(), 1);
5092
5093 let mut all_updates = Vec::new();
5095 add_resources(&mut rr, &mut all_updates, updates, Vec::new());
5096 assert_eq!(all_updates.len(), 3);
5097
5098 let families_hash = StyleFontFamiliesHash::new(core::slice::from_ref(&family));
5099 assert!(rr
5100 .get_font_instance_key(&families_hash, Au::from_px(16.0), dpi)
5101 .is_some());
5102 assert!(rr
5103 .get_font_instance_key(&families_hash, Au::from_px(24.0), dpi)
5104 .is_some());
5105 assert!(rr
5107 .get_font_instance_key(&families_hash, Au::from_px(99.0), dpi)
5108 .is_none());
5109 assert!(rr
5110 .get_font_instance_key(&families_hash, Au::from_px(16.0), DpiScaleFactor::new(2.0))
5111 .is_none());
5112
5113 let key = rr
5115 .get_font_instance_key(&families_hash, Au::from_px(16.0), dpi)
5116 .expect("registered");
5117 let (font_ref, au, got_dpi) = rr
5118 .get_renderable_font_data(&key)
5119 .expect("registered instance must be renderable");
5120 assert_eq!(font_ref.get_hash(), font.get_hash());
5121 assert_eq!(au, Au::from_px(16.0));
5122 assert_eq!(got_dpi, dpi);
5123
5124 let again = build_add_font_resource_updates(
5126 &mut rr,
5127 dpi,
5128 &FcFontCache::default(),
5129 IdNamespace(1),
5130 &fonts,
5131 load_font_none,
5132 parse_font_none,
5133 );
5134 assert!(again.is_empty(), "already-registered fonts must not be re-added");
5135 }
5136
5137 #[test]
5138 fn add_font_msg_into_resource_update_preserves_keys() {
5139 let font = dummy_font_ref();
5140 let key = FontKey::unique(IdNamespace(3));
5141 let family_hash = StyleFontFamilyHash::new(&StyleFontFamily::Ref(font.clone()));
5142 let msg = AddFontMsg::Font(key, family_hash, font.clone());
5143 match msg.into_resource_update() {
5144 ResourceUpdate::AddFont(add) => {
5145 assert_eq!(add.key, key);
5146 assert_eq!(add.font.get_hash(), font.get_hash());
5147 }
5148 other => panic!("expected AddFont, got {other:?}"),
5149 }
5150 }
5151
5152 #[test]
5153 fn delete_font_msg_into_resource_update_preserves_keys() {
5154 let fk = FontKey::unique(IdNamespace(1));
5155 match DeleteFontMsg::Font(fk).into_resource_update() {
5156 ResourceUpdate::DeleteFont(k) => assert_eq!(k, fk),
5157 other => panic!("expected DeleteFont, got {other:?}"),
5158 }
5159 let fik = FontInstanceKey::unique(IdNamespace(1));
5160 let size = (Au::from_px(16.0), DpiScaleFactor::new(1.0));
5161 match DeleteFontMsg::Instance(fik, size).into_resource_update() {
5162 ResourceUpdate::DeleteFontInstance(k) => assert_eq!(k, fik),
5163 other => panic!("expected DeleteFontInstance, got {other:?}"),
5164 }
5165 }
5166
5167 #[test]
5168 fn add_image_msg_into_resource_update_preserves_the_key_and_descriptor() {
5169 let key = ImageKey::unique(IdNamespace(2));
5170 let descriptor = ImageDescriptor {
5171 format: RawImageFormat::BGRA8,
5172 width: 3,
5173 height: 5,
5174 stride: None.into(),
5175 offset: 0,
5176 flags: ImageDescriptorFlags {
5177 is_opaque: false,
5178 allow_mipmaps: true,
5179 },
5180 };
5181 let msg = AddImageMsg(AddImage {
5182 key,
5183 descriptor,
5184 data: ImageData::Raw(SharedRawImageData::new(vec![0u8; 60].into())),
5185 tiling: None,
5186 });
5187 match msg.into_resource_update() {
5188 ResourceUpdate::AddImage(add) => {
5189 assert_eq!(add.key, key);
5190 assert_eq!(add.descriptor, descriptor);
5191 assert!(add.tiling.is_none());
5192 }
5193 other => panic!("expected AddImage, got {other:?}"),
5194 }
5195 }
5196
5197 #[test]
5198 fn build_add_image_resource_updates_skips_null_and_callback_images() {
5199 let rr = RendererResources::default();
5201 let mut images = FastBTreeSet::new();
5202 images.insert(ImageRef::null_image(4, 4, RawImageFormat::RGBA8, Vec::new()));
5203 images.insert(ImageRef::callback(0usize, RefAny::new(0u8)));
5204
5205 let updates = build_add_image_resource_updates(
5206 &rr,
5207 IdNamespace(1),
5208 Epoch::new(),
5209 &test_document_id(),
5210 &images,
5211 store_gl_texture_noop,
5212 );
5213 assert!(updates.is_empty());
5214
5215 let empty = FastBTreeSet::new();
5217 assert!(build_add_image_resource_updates(
5218 &rr,
5219 IdNamespace(1),
5220 Epoch::new(),
5221 &test_document_id(),
5222 &empty,
5223 store_gl_texture_noop,
5224 )
5225 .is_empty());
5226 }
5227
5228 #[test]
5229 fn build_add_image_resource_updates_then_add_resources_round_trip() {
5230 let mut rr = RendererResources::default();
5231 let img = ImageRef::new_rawimage(rgba8_image(2, 2)).expect("valid 2x2");
5232 let hash = img.get_hash();
5233 let ns = IdNamespace(11);
5234
5235 let mut images = FastBTreeSet::new();
5236 images.insert(img.clone());
5237
5238 let updates = build_add_image_resource_updates(
5239 &rr,
5240 ns,
5241 Epoch::new(),
5242 &test_document_id(),
5243 &images,
5244 store_gl_texture_noop,
5245 );
5246 assert_eq!(updates.len(), 1);
5247 assert_eq!(updates[0].0, hash);
5248 assert_eq!(updates[0].1 .0.key, image_ref_hash_to_image_key(hash, ns));
5250 assert_eq!(updates[0].1 .0.descriptor.width, 2);
5251 assert_eq!(updates[0].1 .0.descriptor.height, 2);
5252
5253 let key = updates[0].1 .0.key;
5254 let mut all_updates = Vec::new();
5255 add_resources(&mut rr, &mut all_updates, Vec::new(), updates);
5256 assert_eq!(all_updates.len(), 1);
5257 assert!(matches!(all_updates[0], ResourceUpdate::AddImage(_)));
5258
5259 assert_eq!(rr.get_image(&hash).map(|r| r.key), Some(key));
5261 assert_eq!(rr.image_key_map.get(&key), Some(&hash));
5262
5263 let again = build_add_image_resource_updates(
5265 &rr,
5266 ns,
5267 Epoch::new(),
5268 &test_document_id(),
5269 &images,
5270 store_gl_texture_noop,
5271 );
5272 assert!(again.is_empty());
5273
5274 let new_descriptor = ImageDescriptor {
5276 format: RawImageFormat::BGRA8,
5277 width: 8,
5278 height: 8,
5279 stride: None.into(),
5280 offset: 0,
5281 flags: ImageDescriptorFlags {
5282 is_opaque: true,
5283 allow_mipmaps: true,
5284 },
5285 };
5286 rr.update_image(&hash, new_descriptor);
5287 assert_eq!(rr.get_image(&hash).map(|r| r.descriptor.width), Some(8));
5288 assert_eq!(rr.get_image(&hash).map(|r| r.key), Some(key));
5289 rr.update_image(&ImageRefHash { inner: u64::MAX }, new_descriptor);
5291 }
5292
5293 #[test]
5294 fn add_resources_with_empty_input_changes_nothing() {
5295 let mut rr = RendererResources::default();
5296 let mut updates = Vec::new();
5297 add_resources(&mut rr, &mut updates, Vec::new(), Vec::new());
5298 assert!(updates.is_empty());
5299 assert!(rr.currently_registered_images.is_empty());
5300 assert!(rr.currently_registered_fonts.is_empty());
5301 assert!(rr.image_key_map.is_empty());
5302 }
5303
5304 #[test]
5309 fn paint_dot_composites_at_the_center_and_leaves_far_pixels_alone() {
5310 let mut img = rgba8_image(4, 4);
5311 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5312 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5313
5314 let idx = (4 + 1) * 4;
5316 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255], "center pixel must be opaque red");
5317 assert_eq!(&px[0..4], &[0, 0, 0, 0], "pixels beyond the radius stay untouched");
5319 }
5320
5321 #[test]
5322 fn paint_dot_honours_bgra_channel_order() {
5323 let mut img = rgba8_image(4, 4);
5324 img.data_format = RawImageFormat::BGRA8;
5325 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5326 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5327 let idx = (4 + 1) * 4;
5328 assert_eq!(&px[idx..idx + 4], &[0, 0, 255, 255]);
5330 }
5331
5332 #[test]
5333 fn paint_dot_rejects_degenerate_radii_and_sizes() {
5334 let untouched = |img: &RawImage| {
5335 img.pixels
5336 .get_u8_vec_ref()
5337 .expect("u8")
5338 .as_ref()
5339 .iter()
5340 .all(|b| *b == 0)
5341 };
5342
5343 for r in [0.0_f32, -1.0, -0.0, f32::NAN, f32::NEG_INFINITY] {
5345 let mut img = rgba8_image(4, 4);
5346 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), r));
5347 assert!(untouched(&img), "radius {r} must not paint");
5348 }
5349
5350 let mut img = rgba8_image(0, 0);
5352 img.paint_dot(0.0, 0.0, Brush::new(opaque_red(), 4.0));
5353 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(0));
5354
5355 for format in [
5357 RawImageFormat::R8,
5358 RawImageFormat::RGB8,
5359 RawImageFormat::RGBA16,
5360 RawImageFormat::RGBAF32,
5361 ] {
5362 let mut img = rgba8_image(4, 4);
5363 img.data_format = format;
5364 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5365 assert!(untouched(&img), "format {format:?} must not be painted");
5366 }
5367 }
5368
5369 #[test]
5370 fn paint_dot_with_nan_and_infinite_coordinates_is_a_safe_noop() {
5371 for (cx, cy) in [
5374 (f32::NAN, 2.0_f32),
5375 (2.0, f32::NAN),
5376 (f32::NAN, f32::NAN),
5377 (f32::INFINITY, 2.0),
5378 (f32::NEG_INFINITY, 2.0),
5379 (2.0, f32::INFINITY),
5380 (1.0e30, 1.0e30),
5381 (-1.0e30, -1.0e30),
5382 ] {
5383 let mut img = rgba8_image(4, 4);
5384 img.paint_dot(cx, cy, Brush::new(opaque_red(), 2.0));
5385 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5386 assert!(
5387 px.iter().all(|b| *b == 0),
5388 "({cx}, {cy}) must not paint anything"
5389 );
5390 }
5391 }
5392
5393 #[test]
5394 fn paint_dot_alpha_saturates_and_never_overflows() {
5395 let mut img = rgba8_image(4, 4);
5397 let brush = Brush::new(opaque_red(), 2.0);
5398 for _ in 0..50 {
5399 img.paint_dot(2.0, 2.0, brush);
5400 }
5401 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5402 let idx = (4 + 1) * 4;
5403 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255]);
5404
5405 let mut img = rgba8_image(4, 4);
5410 let mut nan_brush = Brush::new(opaque_red(), 2.0);
5411 nan_brush.hardness = f32::NAN;
5412 img.paint_dot(2.0, 2.0, nan_brush);
5413 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(64));
5416 }
5417
5418 #[test]
5419 fn paint_dot_zero_flow_and_transparent_color_do_not_paint() {
5420 let mut img = rgba8_image(4, 4);
5421 let mut brush = Brush::new(opaque_red(), 2.0);
5422 brush.flow = 0.0;
5423 img.paint_dot(2.0, 2.0, brush);
5424 assert!(img
5425 .pixels
5426 .get_u8_vec_ref()
5427 .expect("u8")
5428 .as_ref()
5429 .iter()
5430 .all(|b| *b == 0));
5431
5432 let mut img = rgba8_image(4, 4);
5433 let transparent = ColorU {
5434 r: 255,
5435 g: 0,
5436 b: 0,
5437 a: 0,
5438 };
5439 img.paint_dot(2.0, 2.0, Brush::new(transparent, 2.0));
5440 assert!(img
5441 .pixels
5442 .get_u8_vec_ref()
5443 .expect("u8")
5444 .as_ref()
5445 .iter()
5446 .all(|b| *b == 0));
5447
5448 let mut img = rgba8_image(4, 4);
5450 let mut brush = Brush::new(opaque_red(), 2.0);
5451 brush.flow = -5.0;
5452 img.paint_dot(2.0, 2.0, brush);
5453 assert!(img
5454 .pixels
5455 .get_u8_vec_ref()
5456 .expect("u8")
5457 .as_ref()
5458 .iter()
5459 .all(|b| *b == 0));
5460 }
5461
5462 #[test]
5463 fn paint_stroke_paints_both_endpoints() {
5464 let mut img = rgba8_image(8, 8);
5465 img.paint_stroke(1.5, 1.5, 6.5, 6.5, Brush::new(opaque_red(), 1.5));
5466 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5467 let alpha_at = |x: usize, y: usize| px[(y * 8 + x) * 4 + 3];
5468 assert!(alpha_at(1, 1) > 0, "start of the stroke must be painted");
5469 assert!(alpha_at(6, 6) > 0, "end of the stroke must be painted");
5470 assert_eq!(alpha_at(7, 0), 0, "off-line pixels stay untouched");
5471 }
5472
5473 #[test]
5474 fn paint_stroke_zero_length_stamps_a_single_dab() {
5475 let mut img = rgba8_image(4, 4);
5477 img.paint_stroke(2.0, 2.0, 2.0, 2.0, Brush::new(opaque_red(), 2.0));
5478 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5479 let idx = (4 + 1) * 4;
5480 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255]);
5481 }
5482
5483 #[test]
5484 fn paint_stroke_degenerate_brush_params_do_not_divide_by_zero_or_hang() {
5485 for spacing in [0.0_f32, -1.0, f32::NAN] {
5488 let mut img = rgba8_image(8, 8);
5489 let mut brush = Brush::new(opaque_red(), 2.0);
5490 brush.spacing = spacing;
5491 img.paint_stroke(0.0, 0.0, 7.0, 7.0, brush);
5492 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(8 * 8 * 4));
5493 }
5494
5495 let mut img = rgba8_image(4, 4);
5497 img.paint_stroke(f32::NAN, 0.0, 1.0, 1.0, Brush::new(opaque_red(), 1.0));
5498 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(64));
5499
5500 let mut img = rgba8_image(4, 4);
5502 img.paint_stroke(0.0, 0.0, 3.0, 3.0, Brush::new(opaque_red(), 0.0));
5503 assert!(img
5504 .pixels
5505 .get_u8_vec_ref()
5506 .expect("u8")
5507 .as_ref()
5508 .iter()
5509 .all(|b| *b == 0));
5510 }
5511
5512 #[test]
5513 fn bug_paint_stroke_with_infinite_endpoint_loops_2_billion_times() {
5514 let mut img = rgba8_image(4, 4);
5518 img.paint_stroke(0.0, 0.0, f32::INFINITY, 0.0, Brush::new(opaque_red(), 2.0));
5519 }
5520
5521 #[test]
5522 fn bug_paint_dot_indexes_out_of_bounds_when_dims_exceed_the_buffer() {
5523 let mut img = RawImage {
5528 pixels: RawImageData::U8(vec![0u8; 4].into()),
5529 width: 100,
5530 height: 100,
5531 premultiplied_alpha: true,
5532 data_format: RawImageFormat::RGBA8,
5533 tag: Vec::new().into(),
5534 };
5535 img.paint_dot(50.0, 50.0, Brush::new(opaque_red(), 4.0));
5536 }
5537
5538 #[test]
5539 fn bug_into_loaded_image_source_overflows_on_huge_dimensions() {
5540 let img = RawImage {
5544 pixels: RawImageData::U8(vec![0u8; 4].into()),
5545 width: usize::MAX,
5546 height: 2,
5547 premultiplied_alpha: true,
5548 data_format: RawImageFormat::RGBA8,
5549 tag: Vec::new().into(),
5550 };
5551 assert!(img.into_loaded_image_source().is_none());
5552 }
5553}