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 expected_len.checked_mul(FOUR_BPP)?;
1931
1932 let (bytes, data_format, is_opaque): (U8Vec, RawImageFormat, bool) = match data_format {
1933 RawImageFormat::R8 => {
1934 let (bytes, is_opaque) = Self::load_r8(pixels, expected_len)?;
1935 (bytes, RawImageFormat::R8, is_opaque)
1936 }
1937 RawImageFormat::RG8 => {
1938 let (bytes, is_opaque) = Self::load_rg8(pixels, expected_len, premultiplied_alpha)?;
1939 (bytes, RawImageFormat::BGRA8, is_opaque)
1940 }
1941 RawImageFormat::RGB8 => {
1942 let (bytes, is_opaque) = Self::load_rgb8(pixels, expected_len)?;
1943 (bytes, RawImageFormat::BGRA8, is_opaque)
1944 }
1945 RawImageFormat::RGBA8 => {
1946 let (bytes, is_opaque) = Self::load_rgba8(pixels, expected_len, premultiplied_alpha)?;
1947 (bytes, RawImageFormat::BGRA8, is_opaque)
1948 }
1949 RawImageFormat::R16 => {
1950 let (bytes, is_opaque) = Self::load_r16(pixels, expected_len)?;
1951 (bytes, RawImageFormat::BGRA8, is_opaque)
1952 }
1953 RawImageFormat::RG16 => {
1954 let (bytes, is_opaque) = Self::load_rg16(pixels, expected_len)?;
1955 (bytes, RawImageFormat::BGRA8, is_opaque)
1956 }
1957 RawImageFormat::RGB16 => {
1958 let (bytes, is_opaque) = Self::load_rgb16(pixels, expected_len)?;
1959 (bytes, RawImageFormat::BGRA8, is_opaque)
1960 }
1961 RawImageFormat::RGBA16 => {
1962 let (bytes, is_opaque) =
1963 Self::load_rgba16(pixels, expected_len, premultiplied_alpha)?;
1964 (bytes, RawImageFormat::BGRA8, is_opaque)
1965 }
1966 RawImageFormat::BGR8 => {
1967 let (bytes, is_opaque) = Self::load_bgr8(pixels, expected_len)?;
1968 (bytes, RawImageFormat::BGRA8, is_opaque)
1969 }
1970 RawImageFormat::BGRA8 => {
1971 let (bytes, is_opaque) = Self::load_bgra8(pixels, expected_len, premultiplied_alpha)?;
1972 (bytes, RawImageFormat::BGRA8, is_opaque)
1973 }
1974 RawImageFormat::RGBF32 => {
1975 let (bytes, is_opaque) = Self::load_rgbf32(pixels, expected_len)?;
1976 (bytes, RawImageFormat::BGRA8, is_opaque)
1977 }
1978 RawImageFormat::RGBAF32 => {
1979 let (bytes, is_opaque) =
1980 Self::load_rgbaf32(pixels, expected_len, premultiplied_alpha)?;
1981 (bytes, RawImageFormat::BGRA8, is_opaque)
1982 }
1983 };
1984
1985 let image_data = ImageData::Raw(SharedRawImageData::new(bytes));
1986 let image_descriptor = ImageDescriptor {
1987 format: data_format,
1988 width,
1989 height,
1990 offset: 0,
1991 stride: None.into(),
1992 flags: ImageDescriptorFlags {
1993 is_opaque,
1994 allow_mipmaps: true,
1995 },
1996 };
1997
1998 Some((image_data, image_descriptor))
1999 }
2000
2001 fn load_r8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2005 let pixels = pixels.get_u8_vec()?;
2006
2007 if pixels.len() != expected_len {
2008 return None;
2009 }
2010
2011 Some((pixels, false))
2012 }
2013
2014 fn load_rg8(
2015 pixels: RawImageData,
2016 expected_len: usize,
2017 premultiplied_alpha: bool,
2018 ) -> Option<(U8Vec, bool)> {
2019 let pixels = pixels.get_u8_vec()?;
2020
2021 if pixels.len() != expected_len * TWO_CHANNELS {
2022 return None;
2023 }
2024
2025 let mut is_opaque = true;
2026 let mut px = vec![0; expected_len * FOUR_BPP];
2027
2028 for (pixel_index, greyalpha) in pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate() {
2030 let grey = greyalpha[0];
2031 let alpha = greyalpha[1];
2032
2033 if alpha != 255 {
2034 is_opaque = false;
2035 }
2036
2037 px[pixel_index * FOUR_BPP] = grey;
2038 px[(pixel_index * FOUR_BPP) + 1] = grey;
2039 px[(pixel_index * FOUR_BPP) + 2] = grey;
2040 px[(pixel_index * FOUR_BPP) + 3] = alpha;
2041
2042 if !premultiplied_alpha {
2043 premultiply_alpha(
2044 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2045 );
2046 }
2047 }
2048
2049 Some((px.into(), is_opaque))
2050 }
2051
2052 fn load_rgb8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2053 let pixels = pixels.get_u8_vec()?;
2054
2055 if pixels.len() != expected_len * THREE_CHANNELS {
2056 return None;
2057 }
2058
2059 let mut px = vec![0; expected_len * FOUR_BPP];
2060
2061 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2063 let red = rgb[0];
2064 let green = rgb[1];
2065 let blue = rgb[2];
2066
2067 px[pixel_index * FOUR_BPP] = blue;
2068 px[(pixel_index * FOUR_BPP) + 1] = green;
2069 px[(pixel_index * FOUR_BPP) + 2] = red;
2070 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2071 }
2072
2073 Some((px.into(), true))
2074 }
2075
2076 fn load_rgba8(
2077 pixels: RawImageData,
2078 expected_len: usize,
2079 premultiplied_alpha: bool,
2080 ) -> Option<(U8Vec, bool)> {
2081 let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
2082
2083 if pixels.len() != expected_len * FOUR_CHANNELS {
2084 return None;
2085 }
2086
2087 let mut is_opaque = true;
2088
2089 if premultiplied_alpha {
2092 for rgba in pixels.chunks_exact_mut(4) {
2093 let (r, gba) = rgba.split_first_mut()?;
2094 core::mem::swap(r, gba.get_mut(1)?);
2095 let a = rgba.get_mut(3)?;
2096 if *a != 255 {
2097 is_opaque = false;
2098 }
2099 }
2100 } else {
2101 for rgba in pixels.chunks_exact_mut(4) {
2102 let (r, gba) = rgba.split_first_mut()?;
2104 core::mem::swap(r, gba.get_mut(1)?);
2105 let a = rgba.get_mut(3)?;
2106 if *a != 255 {
2107 is_opaque = false;
2108 }
2109 premultiply_alpha(rgba); }
2111 }
2112
2113 Some((pixels.into(), is_opaque))
2114 }
2115
2116 fn load_r16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2117 let pixels = pixels.get_u16_vec()?;
2118
2119 if pixels.len() != expected_len {
2120 return None;
2121 }
2122
2123 let mut px = vec![0; expected_len * FOUR_BPP];
2124
2125 for (pixel_index, grey_u16) in pixels.as_ref().iter().enumerate() {
2127 let grey_u8 = normalize_u16(*grey_u16);
2128 px[pixel_index * FOUR_BPP] = grey_u8;
2129 px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
2130 px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
2131 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2132 }
2133
2134 Some((px.into(), true))
2135 }
2136
2137 fn load_rg16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2138 let pixels = pixels.get_u16_vec()?;
2139
2140 if pixels.len() != expected_len * TWO_CHANNELS {
2141 return None;
2142 }
2143
2144 let mut is_opaque = true;
2145 let mut px = vec![0; expected_len * FOUR_BPP];
2146
2147 for (pixel_index, greyalpha) in pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate() {
2149 let grey_u8 = normalize_u16(greyalpha[0]);
2150 let alpha_u8 = normalize_u16(greyalpha[1]);
2151
2152 if alpha_u8 != 255 {
2153 is_opaque = false;
2154 }
2155
2156 px[pixel_index * FOUR_BPP] = grey_u8;
2157 px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
2158 px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
2159 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2160 }
2161
2162 Some((px.into(), is_opaque))
2163 }
2164
2165 fn load_rgb16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2166 let pixels = pixels.get_u16_vec()?;
2167
2168 if pixels.len() != expected_len * THREE_CHANNELS {
2169 return None;
2170 }
2171
2172 let mut px = vec![0; expected_len * FOUR_BPP];
2173
2174 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2176 let red_u8 = normalize_u16(rgb[0]);
2177 let green_u8 = normalize_u16(rgb[1]);
2178 let blue_u8 = normalize_u16(rgb[2]);
2179
2180 px[pixel_index * FOUR_BPP] = blue_u8;
2181 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2182 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2183 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2184 }
2185
2186 Some((px.into(), true))
2187 }
2188
2189 fn load_rgba16(
2190 pixels: RawImageData,
2191 expected_len: usize,
2192 premultiplied_alpha: bool,
2193 ) -> Option<(U8Vec, bool)> {
2194 let pixels = pixels.get_u16_vec()?;
2195
2196 if pixels.len() != expected_len * FOUR_CHANNELS {
2197 return None;
2198 }
2199
2200 let mut is_opaque = true;
2201 let mut px = vec![0; expected_len * FOUR_BPP];
2202
2203 if premultiplied_alpha {
2205 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2206 let red_u8 = normalize_u16(rgba[0]);
2207 let green_u8 = normalize_u16(rgba[1]);
2208 let blue_u8 = normalize_u16(rgba[2]);
2209 let alpha_u8 = normalize_u16(rgba[3]);
2210
2211 if alpha_u8 != 255 {
2212 is_opaque = false;
2213 }
2214
2215 px[pixel_index * FOUR_BPP] = blue_u8;
2216 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2217 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2218 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2219 }
2220 } else {
2221 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2222 let red_u8 = normalize_u16(rgba[0]);
2223 let green_u8 = normalize_u16(rgba[1]);
2224 let blue_u8 = normalize_u16(rgba[2]);
2225 let alpha_u8 = normalize_u16(rgba[3]);
2226
2227 if alpha_u8 != 255 {
2228 is_opaque = false;
2229 }
2230
2231 px[pixel_index * FOUR_BPP] = blue_u8;
2232 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2233 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2234 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2235 premultiply_alpha(
2236 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2237 );
2238 }
2239 }
2240
2241 Some((px.into(), is_opaque))
2242 }
2243
2244 fn load_bgr8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2245 let pixels = pixels.get_u8_vec()?;
2246
2247 if pixels.len() != expected_len * THREE_CHANNELS {
2248 return None;
2249 }
2250
2251 let mut px = vec![0; expected_len * FOUR_BPP];
2252
2253 for (pixel_index, bgr) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2255 let blue = bgr[0];
2256 let green = bgr[1];
2257 let red = bgr[2];
2258
2259 px[pixel_index * FOUR_BPP] = blue;
2260 px[(pixel_index * FOUR_BPP) + 1] = green;
2261 px[(pixel_index * FOUR_BPP) + 2] = red;
2262 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2263 }
2264
2265 Some((px.into(), true))
2266 }
2267
2268 fn load_bgra8(
2269 pixels: RawImageData,
2270 expected_len: usize,
2271 premultiplied_alpha: bool,
2272 ) -> Option<(U8Vec, bool)> {
2273 let mut is_opaque = true;
2274
2275 let bytes: U8Vec = if premultiplied_alpha {
2276 let pixels = pixels.get_u8_vec()?;
2278
2279 if pixels.len() != expected_len * FOUR_BPP {
2280 return None;
2281 }
2282
2283 is_opaque = pixels
2284 .as_ref()
2285 .chunks_exact(FOUR_CHANNELS)
2286 .all(|bgra| bgra[3] == 255);
2287
2288 pixels
2289 } else {
2290 let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
2291
2292 if pixels.len() != expected_len * FOUR_BPP {
2293 return None;
2294 }
2295
2296 for bgra in pixels.chunks_exact_mut(FOUR_CHANNELS) {
2297 if bgra[3] != 255 {
2298 is_opaque = false;
2299 }
2300 premultiply_alpha(bgra);
2301 }
2302 pixels.into()
2303 };
2304
2305 Some((bytes, is_opaque))
2306 }
2307
2308 #[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)> {
2312 let pixels = pixels.get_f32_vec_ref()?;
2313
2314 if pixels.len() != expected_len * THREE_CHANNELS {
2315 return None;
2316 }
2317
2318 let mut px = vec![0; expected_len * FOUR_BPP];
2319
2320 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2322 let red_u8 = (rgb[0] * 255.0) as u8;
2323 let green_u8 = (rgb[1] * 255.0) as u8;
2324 let blue_u8 = (rgb[2] * 255.0) as u8;
2325
2326 px[pixel_index * FOUR_BPP] = blue_u8;
2327 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2328 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2329 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2330 }
2331
2332 Some((px.into(), true))
2333 }
2334
2335 #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] #[allow(clippy::needless_pass_by_value)] fn load_rgbaf32(
2339 pixels: RawImageData,
2340 expected_len: usize,
2341 premultiplied_alpha: bool,
2342 ) -> Option<(U8Vec, bool)> {
2343 let pixels = pixels.get_f32_vec_ref()?;
2344
2345 if pixels.len() != expected_len * FOUR_CHANNELS {
2346 return None;
2347 }
2348
2349 let mut is_opaque = true;
2350 let mut px = vec![0; expected_len * FOUR_BPP];
2351
2352 if premultiplied_alpha {
2354 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2355 let red_u8 = (rgba[0] * 255.0) as u8;
2356 let green_u8 = (rgba[1] * 255.0) as u8;
2357 let blue_u8 = (rgba[2] * 255.0) as u8;
2358 let alpha_u8 = (rgba[3] * 255.0) as u8;
2359
2360 if alpha_u8 != 255 {
2361 is_opaque = false;
2362 }
2363
2364 px[pixel_index * FOUR_BPP] = blue_u8;
2365 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2366 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2367 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2368 }
2369 } else {
2370 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2371 let red_u8 = (rgba[0] * 255.0) as u8;
2372 let green_u8 = (rgba[1] * 255.0) as u8;
2373 let blue_u8 = (rgba[2] * 255.0) as u8;
2374 let alpha_u8 = (rgba[3] * 255.0) as u8;
2375
2376 if alpha_u8 != 255 {
2377 is_opaque = false;
2378 }
2379
2380 px[pixel_index * FOUR_BPP] = blue_u8;
2381 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2382 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2383 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2384 premultiply_alpha(
2385 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2386 );
2387 }
2388 }
2389
2390 Some((px.into(), is_opaque))
2391 }
2392}
2393
2394impl_option!(
2395 RawImage,
2396 OptionRawImage,
2397 copy = false,
2398 [Debug, Clone, PartialEq, PartialOrd]
2399);
2400
2401#[must_use] pub fn font_size_to_au(font_size: StyleFontSize) -> Au {
2402 Au::from_px(font_size.inner.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE))
2403}
2404
2405pub type FontInstanceFlags = u32;
2406
2407pub const FONT_INSTANCE_FLAG_SYNTHETIC_BOLD: u32 = 1 << 1;
2409pub const FONT_INSTANCE_FLAG_EMBEDDED_BITMAPS: u32 = 1 << 2;
2410pub const FONT_INSTANCE_FLAG_SUBPIXEL_BGR: u32 = 1 << 3;
2411pub const FONT_INSTANCE_FLAG_TRANSPOSE: u32 = 1 << 4;
2412pub const FONT_INSTANCE_FLAG_FLIP_X: u32 = 1 << 5;
2413pub const FONT_INSTANCE_FLAG_FLIP_Y: u32 = 1 << 6;
2414pub const FONT_INSTANCE_FLAG_SUBPIXEL_POSITION: u32 = 1 << 7;
2415
2416pub const FONT_INSTANCE_FLAG_FORCE_GDI: u32 = 1 << 16;
2418
2419pub const FONT_INSTANCE_FLAG_FONT_SMOOTHING: u32 = 1 << 16;
2421
2422pub const FONT_INSTANCE_FLAG_FORCE_AUTOHINT: u32 = 1 << 16;
2424pub const FONT_INSTANCE_FLAG_NO_AUTOHINT: u32 = 1 << 17;
2425pub const FONT_INSTANCE_FLAG_VERTICAL_LAYOUT: u32 = 1 << 18;
2426pub const FONT_INSTANCE_FLAG_LCD_VERTICAL: u32 = 1 << 19;
2427
2428#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2429pub struct GlyphOptions {
2430 pub render_mode: FontRenderMode,
2431 pub flags: FontInstanceFlags,
2432}
2433
2434#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2435pub enum FontRenderMode {
2436 Mono,
2437 Alpha,
2438 Subpixel,
2439}
2440
2441#[cfg(target_arch = "wasm32")]
2442#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2443pub struct FontInstancePlatformOptions {
2444 }
2446
2447#[cfg(target_os = "windows")]
2448#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2449pub struct FontInstancePlatformOptions {
2450 pub gamma: u16,
2451 pub contrast: u8,
2452 pub cleartype_level: u8,
2453}
2454
2455#[cfg(target_os = "macos")]
2456#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2457pub struct FontInstancePlatformOptions {
2458 pub unused: u32,
2459}
2460
2461#[cfg(target_os = "linux")]
2462#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2463pub struct FontInstancePlatformOptions {
2464 pub lcd_filter: FontLCDFilter,
2465 pub hinting: FontHinting,
2466}
2467
2468#[cfg(any(target_os = "android", target_os = "ios"))]
2472#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2473pub struct FontInstancePlatformOptions {
2474 pub unused: u32,
2475}
2476
2477#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2478pub enum FontHinting {
2479 None,
2480 Mono,
2481 Light,
2482 Normal,
2483 LCD,
2484}
2485
2486#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2487#[derive(Default)]
2488pub enum FontLCDFilter {
2489 None,
2490 #[default]
2491 Default,
2492 Light,
2493 Legacy,
2494}
2495
2496
2497#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2498pub struct FontInstanceOptions {
2499 pub render_mode: FontRenderMode,
2500 pub flags: FontInstanceFlags,
2501 pub bg_color: ColorU,
2502 pub synthetic_italics: SyntheticItalics,
2506}
2507
2508impl Default for FontInstanceOptions {
2509 fn default() -> Self {
2510 Self {
2511 render_mode: FontRenderMode::Subpixel,
2512 flags: 0,
2513 bg_color: ColorU::TRANSPARENT,
2514 synthetic_italics: SyntheticItalics::default(),
2515 }
2516 }
2517}
2518
2519#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2520#[derive(Default)]
2521pub struct SyntheticItalics {
2522 pub angle: i16,
2523}
2524
2525
2526#[derive(Debug)]
2532#[repr(C)]
2533pub struct SharedRawImageData {
2534 pub data: *const U8Vec,
2536 pub copies: *const AtomicUsize,
2538 pub run_destructor: bool,
2540}
2541
2542impl SharedRawImageData {
2543 #[must_use] pub fn new(data: U8Vec) -> Self {
2545 Self {
2546 data: Box::into_raw(Box::new(data)),
2547 copies: Box::into_raw(Box::new(AtomicUsize::new(1))),
2548 run_destructor: true,
2549 }
2550 }
2551
2552 #[must_use] pub fn as_ref(&self) -> &[u8] {
2554 unsafe { (*self.data).as_ref() }
2557 }
2558
2559 #[must_use] pub fn get_bytes(&self) -> &[u8] {
2561 self.as_ref()
2562 }
2563
2564 #[must_use] pub fn as_ptr(&self) -> *const u8 {
2566 unsafe { (*self.data).as_ref().as_ptr() }
2568 }
2569
2570 #[must_use] pub const fn len(&self) -> usize {
2572 unsafe { (*self.data).len() }
2574 }
2575
2576 #[must_use] pub const fn is_empty(&self) -> bool {
2578 self.len() == 0
2579 }
2580
2581 #[must_use] pub fn into_inner(self) -> Option<U8Vec> {
2584 unsafe {
2588 if self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) == Some(1) {
2589 let data = Box::from_raw(self.data.cast_mut());
2590 drop(Box::from_raw(self.copies.cast_mut()));
2591 core::mem::forget(self); Some(*data)
2593 } else {
2594 None
2595 }
2596 }
2597 }
2598}
2599
2600unsafe impl Send for SharedRawImageData {}
2603unsafe impl Sync for SharedRawImageData {}
2604
2605impl Clone for SharedRawImageData {
2606 fn clone(&self) -> Self {
2607 unsafe {
2610 self.copies
2611 .as_ref()
2612 .map(|m| m.fetch_add(1, AtomicOrdering::SeqCst));
2613 }
2614 Self {
2615 data: self.data,
2616 copies: self.copies,
2617 run_destructor: true,
2618 }
2619 }
2620}
2621
2622impl Drop for SharedRawImageData {
2623 fn drop(&mut self) {
2624 self.run_destructor = false;
2625 unsafe {
2629 let copies = (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst);
2630 if copies == 1 {
2631 drop(Box::from_raw(self.data.cast_mut()));
2632 drop(Box::from_raw(self.copies.cast_mut()));
2633 }
2634 }
2635 }
2636}
2637
2638impl PartialEq for SharedRawImageData {
2639 fn eq(&self, rhs: &Self) -> bool {
2640 core::ptr::eq(self.data, rhs.data)
2641 }
2642}
2643
2644impl Eq for SharedRawImageData {}
2645
2646impl PartialOrd for SharedRawImageData {
2647 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
2648 Some(self.cmp(other))
2649 }
2650}
2651
2652impl Ord for SharedRawImageData {
2653 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
2654 (self.data as usize).cmp(&(other.data as usize))
2655 }
2656}
2657
2658impl Hash for SharedRawImageData {
2659 fn hash<H>(&self, state: &mut H)
2660 where
2661 H: Hasher,
2662 {
2663 (self.data as usize).hash(state);
2664 }
2665}
2666
2667#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2670#[repr(C, u8)]
2671pub enum ImageData {
2672 Raw(SharedRawImageData),
2675 External(ExternalImageData),
2678}
2679
2680#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
2682#[repr(C, u8)]
2683pub enum ExternalImageType {
2684 TextureHandle(ImageBufferKind),
2686 Buffer,
2688}
2689
2690#[repr(C)]
2694#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
2695pub struct ExternalImageId {
2696 pub inner: u64,
2697}
2698
2699static LAST_EXTERNAL_IMAGE_ID: AtomicUsize = AtomicUsize::new(0);
2700
2701impl Default for ExternalImageId {
2702 fn default() -> Self {
2703 Self::new()
2704 }
2705}
2706
2707impl ExternalImageId {
2708 pub fn new() -> Self {
2710 Self {
2711 inner: LAST_EXTERNAL_IMAGE_ID.fetch_add(1, AtomicOrdering::SeqCst) as u64,
2712 }
2713 }
2714}
2715
2716#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2717#[repr(C, u8)]
2718pub enum GlyphOutlineOperation {
2719 MoveTo(OutlineMoveTo),
2720 LineTo(OutlineLineTo),
2721 QuadraticCurveTo(OutlineQuadTo),
2722 CubicCurveTo(OutlineCubicTo),
2723 ClosePath,
2724}
2725
2726impl_option!(
2727 GlyphOutlineOperation,
2728 OptionGlyphOutlineOperation,
2729 copy = false,
2730 [Debug, Clone, PartialEq, Eq, PartialOrd]
2731);
2732
2733#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2735#[repr(C)]
2736pub struct OutlineMoveTo {
2737 pub x: i16,
2738 pub y: i16,
2739}
2740
2741#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2743#[repr(C)]
2744pub struct OutlineLineTo {
2745 pub x: i16,
2746 pub y: i16,
2747}
2748
2749#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2751#[repr(C)]
2752pub struct OutlineQuadTo {
2753 pub ctrl_1_x: i16,
2754 pub ctrl_1_y: i16,
2755 pub end_x: i16,
2756 pub end_y: i16,
2757}
2758
2759#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
2761#[repr(C)]
2762pub struct OutlineCubicTo {
2763 pub ctrl_1_x: i16,
2764 pub ctrl_1_y: i16,
2765 pub ctrl_2_x: i16,
2766 pub ctrl_2_y: i16,
2767 pub end_x: i16,
2768 pub end_y: i16,
2769}
2770
2771#[derive(Debug, Clone, PartialEq, PartialOrd)]
2772#[repr(C)]
2773pub struct GlyphOutline {
2774 pub operations: GlyphOutlineOperationVec,
2775}
2776
2777azul_css::impl_vec!(GlyphOutlineOperation, GlyphOutlineOperationVec, GlyphOutlineOperationVecDestructor, GlyphOutlineOperationVecDestructorType, GlyphOutlineOperationVecSlice, OptionGlyphOutlineOperation);
2778azul_css::impl_vec_clone!(
2779 GlyphOutlineOperation,
2780 GlyphOutlineOperationVec,
2781 GlyphOutlineOperationVecDestructor
2782);
2783azul_css::impl_vec_debug!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2784azul_css::impl_vec_partialord!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2785azul_css::impl_vec_partialeq!(GlyphOutlineOperation, GlyphOutlineOperationVec);
2786
2787#[derive(Debug, Clone, Copy)]
2788#[repr(C)]
2789pub struct OwnedGlyphBoundingBox {
2790 pub max_x: i16,
2791 pub max_y: i16,
2792 pub min_x: i16,
2793 pub min_y: i16,
2794}
2795
2796#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
2798#[repr(C)]
2799pub enum ImageBufferKind {
2800 Texture2D = 0,
2802 TextureRect = 1,
2809 TextureExternal = 2,
2814}
2815
2816#[repr(C)]
2818#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
2819pub struct ExternalImageData {
2820 pub id: ExternalImageId,
2822 pub channel_index: u8,
2825 pub image_type: ExternalImageType,
2827}
2828
2829pub type TileSize = u16;
2830
2831#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
2832pub enum ImageDirtyRect {
2833 All,
2834 Partial(LayoutRect),
2835}
2836
2837#[derive(Debug, Clone, PartialEq, PartialOrd)]
2838pub enum ResourceUpdate {
2839 AddFont(AddFont),
2840 DeleteFont(FontKey),
2841 AddFontInstance(AddFontInstance),
2842 DeleteFontInstance(FontInstanceKey),
2843 AddImage(AddImage),
2844 UpdateImage(UpdateImage),
2845 DeleteImage(ImageKey),
2846}
2847
2848#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2849pub struct AddImage {
2850 pub key: ImageKey,
2851 pub descriptor: ImageDescriptor,
2852 pub data: ImageData,
2853 pub tiling: Option<TileSize>,
2854}
2855
2856#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
2857pub struct UpdateImage {
2858 pub key: ImageKey,
2859 pub descriptor: ImageDescriptor,
2860 pub data: ImageData,
2861 pub dirty_rect: ImageDirtyRect,
2862}
2863
2864#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2867pub struct AddFont {
2868 pub key: FontKey,
2869 pub font: FontRef,
2870}
2871
2872impl fmt::Debug for AddFont {
2873 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2874 write!(
2875 f,
2876 "AddFont {{ key: {:?}, font: {:?} }}",
2877 self.key, self.font
2878 )
2879 }
2880}
2881
2882#[derive(Debug, Clone, PartialEq, PartialOrd)]
2883pub struct AddFontInstance {
2884 pub key: FontInstanceKey,
2885 pub font_key: FontKey,
2886 pub glyph_size: (Au, DpiScaleFactor),
2887 pub options: Option<FontInstanceOptions>,
2888 pub platform_options: Option<FontInstancePlatformOptions>,
2889 pub variations: Vec<FontVariation>,
2890}
2891
2892#[repr(C)]
2893#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
2894pub struct FontVariation {
2895 pub tag: u32,
2896 pub value: f32,
2897}
2898
2899#[repr(C)]
2900#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
2901pub struct Epoch {
2902 inner: u32,
2903}
2904
2905impl fmt::Display for Epoch {
2906 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2907 write!(f, "{}", self.inner)
2908 }
2909}
2910
2911impl Default for Epoch {
2912 fn default() -> Self {
2913 Self::new()
2914 }
2915}
2916
2917impl Epoch {
2918 #[must_use] pub const fn new() -> Self {
2922 Self { inner: 0 }
2923 }
2924 #[must_use] pub const fn from(i: u32) -> Self {
2925 Self { inner: i }
2926 }
2927 #[must_use] pub const fn into_u32(&self) -> u32 {
2928 self.inner
2929 }
2930
2931 pub const fn increment(&mut self) {
2934 use core::u32;
2935 const MAX_ID: u32 = u32::MAX - 1;
2936 *self = match self.inner {
2937 MAX_ID => Self { inner: 0 },
2938 other => Self {
2939 inner: other.saturating_add(1),
2940 },
2941 };
2942 }
2943}
2944
2945#[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Eq, Ord)]
2947pub struct Au(pub i32);
2948
2949pub const AU_PER_PX: i32 = 60;
2950pub const MAX_AU: i32 = (1 << 30) - 1;
2951pub const MIN_AU: i32 = -(1 << 30) - 1;
2952
2953impl Au {
2954 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] #[must_use] pub fn from_px(px: f32) -> Self {
2956 let target_app_units = (px * AU_PER_PX as f32) as i32;
2957 Self(target_app_units.clamp(MIN_AU, MAX_AU))
2958 }
2959 #[allow(clippy::cast_precision_loss)] #[must_use] pub fn into_px(&self) -> f32 {
2961 self.0 as f32 / AU_PER_PX as f32
2962 }
2963}
2964
2965#[derive(Debug)]
2967pub enum AddFontMsg {
2968 Font(FontKey, StyleFontFamilyHash, FontRef),
2970 Instance(AddFontInstance, (Au, DpiScaleFactor)),
2971}
2972
2973impl AddFontMsg {
2974 #[must_use] pub fn into_resource_update(&self) -> ResourceUpdate {
2975 use self::AddFontMsg::{Font, Instance};
2976 match self {
2977 Font(font_key, _, font_ref) => ResourceUpdate::AddFont(AddFont {
2978 key: *font_key,
2979 font: font_ref.clone(),
2980 }),
2981 Instance(fi, _) => ResourceUpdate::AddFontInstance(fi.clone()),
2982 }
2983 }
2984}
2985
2986#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
2987pub enum DeleteFontMsg {
2988 Font(FontKey),
2989 Instance(FontInstanceKey, (Au, DpiScaleFactor)),
2990}
2991
2992impl DeleteFontMsg {
2993 #[must_use] pub const fn into_resource_update(&self) -> ResourceUpdate {
2994 use self::DeleteFontMsg::{Font, Instance};
2995 match self {
2996 Font(f) => ResourceUpdate::DeleteFont(*f),
2997 Instance(fi, _) => ResourceUpdate::DeleteFontInstance(*fi),
2998 }
2999 }
3000}
3001
3002#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
3003pub struct AddImageMsg(pub AddImage);
3004
3005impl AddImageMsg {
3006 #[must_use] pub fn into_resource_update(&self) -> ResourceUpdate {
3007 ResourceUpdate::AddImage(self.0.clone())
3008 }
3009}
3010
3011#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3012#[repr(C)]
3013pub struct LoadedFontSource {
3014 pub data: U8Vec,
3015 pub index: u32,
3016 pub load_outlines: bool,
3017}
3018
3019pub type LoadFontFn = fn(&StyleFontFamily, &FcFontCache) -> Option<LoadedFontSource>;
3021
3022pub type ParseFontFn = fn(LoadedFontSource) -> Option<FontRef>; pub type GlStoreImageFn = fn(DocumentId, Epoch, Texture, ExternalImageId);
3026
3027#[must_use] pub fn texture_external_image_id(dom_id: DomId, node_id: NodeId) -> ExternalImageId {
3034 let dom = dom_id.inner as u64;
3035 let node = node_id.index() as u64;
3036 debug_assert!(u32::try_from(dom).is_ok(), "DomId exceeds 32-bit range");
3037 debug_assert!(u32::try_from(node).is_ok(), "NodeId exceeds 32-bit range");
3038 ExternalImageId {
3039 inner: (dom << 32) | (node & 0xFFFF_FFFF),
3040 }
3041}
3042
3043#[must_use] pub const fn image_ref_hash_to_external_image_id(hash: ImageRefHash) -> ExternalImageId {
3047 ExternalImageId {
3048 inner: hash.inner,
3049 }
3050}
3051
3052#[allow(clippy::too_many_lines)] pub fn build_add_font_resource_updates(
3062 renderer_resources: &mut RendererResources,
3063 dpi: DpiScaleFactor,
3064 fc_cache: &FcFontCache,
3065 id_namespace: IdNamespace,
3066 fonts_in_dom: &OrderedMap<ImmediateFontId, FastBTreeSet<Au>>,
3067 font_source_load_fn: LoadFontFn,
3068 parse_font_fn: ParseFontFn,
3069) -> Vec<(StyleFontFamilyHash, AddFontMsg)> {
3070 let mut resource_updates = Vec::new();
3071 let mut font_instances_added_this_frame = FastBTreeSet::new();
3072
3073 'outer: for (im_font_id, font_sizes) in fonts_in_dom {
3074 macro_rules! insert_font_instances {
3075 ($font_family_hash:expr, $font_key:expr, $font_size:expr) => {{
3076 let font_instance_key_exists = renderer_resources
3077 .currently_registered_fonts
3078 .get(&$font_key)
3079 .and_then(|(_, font_instances)| font_instances.get(&($font_size, dpi)))
3080 .is_some()
3081 || font_instances_added_this_frame.contains(&($font_key, ($font_size, dpi)));
3082
3083 if !font_instance_key_exists {
3084 let font_instance_key = FontInstanceKey::unique(id_namespace);
3085
3086 #[cfg(target_os = "windows")]
3088 let platform_options = FontInstancePlatformOptions {
3089 gamma: 300,
3090 contrast: 100,
3091 cleartype_level: 100,
3092 };
3093
3094 #[cfg(target_os = "linux")]
3095 let platform_options = FontInstancePlatformOptions {
3096 lcd_filter: FontLCDFilter::Default,
3097 hinting: FontHinting::Normal,
3098 };
3099
3100 #[cfg(target_os = "macos")]
3101 let platform_options = FontInstancePlatformOptions::default();
3102
3103 #[cfg(target_arch = "wasm32")]
3104 let platform_options = FontInstancePlatformOptions::default();
3105
3106 #[cfg(any(target_os = "android", target_os = "ios"))]
3107 let platform_options = FontInstancePlatformOptions::default();
3108
3109 let options = FontInstanceOptions {
3110 render_mode: FontRenderMode::Subpixel,
3111 flags: FONT_INSTANCE_FLAG_NO_AUTOHINT,
3112 ..Default::default()
3113 };
3114
3115 font_instances_added_this_frame.insert(($font_key, ($font_size, dpi)));
3116 resource_updates.push((
3117 $font_family_hash,
3118 AddFontMsg::Instance(
3119 AddFontInstance {
3120 key: font_instance_key,
3121 font_key: $font_key,
3122 glyph_size: ($font_size, dpi),
3123 options: Some(options),
3124 platform_options: Some(platform_options),
3125 variations: alloc::vec::Vec::new(),
3126 },
3127 ($font_size, dpi),
3128 ),
3129 ));
3130 }
3131 }};
3132 }
3133
3134 match im_font_id {
3135 ImmediateFontId::Resolved((font_family_hash, font_id)) => {
3136 for font_size in font_sizes {
3139 insert_font_instances!(*font_family_hash, *font_id, *font_size);
3140 }
3141 }
3142 ImmediateFontId::Unresolved(style_font_families) => {
3143 let mut font_family_hash = None;
3153 let font_families_hash = StyleFontFamiliesHash::new(style_font_families.as_ref());
3154
3155 'inner: for family in style_font_families.as_ref() {
3157 let current_family_hash = StyleFontFamilyHash::new(family);
3158
3159 if let Some(font_id) = renderer_resources.font_id_map.get(¤t_family_hash)
3160 {
3161 for font_size in font_sizes {
3163 insert_font_instances!(current_family_hash, *font_id, *font_size);
3164 }
3165 continue 'outer;
3166 }
3167
3168 let font_ref = match family {
3169 StyleFontFamily::Ref(r) => r.clone(), other => {
3171 let Some(font_data) = (font_source_load_fn)(other, fc_cache) else {
3173 continue 'inner;
3174 };
3175
3176
3177
3178 match (parse_font_fn)(font_data) {
3179 Some(s) => s,
3180 None => continue 'inner,
3181 }
3182 }
3183 };
3184
3185 font_family_hash = Some((current_family_hash, font_ref));
3187 break 'inner;
3188 }
3189
3190 let (font_family_hash, font_ref) = match font_family_hash {
3191 None => continue 'outer, Some(s) => s,
3193 };
3194
3195 let font_key = FontKey::unique(id_namespace);
3197 let add_font_msg = AddFontMsg::Font(font_key, font_family_hash, font_ref);
3198
3199 renderer_resources
3200 .font_id_map
3201 .insert(font_family_hash, font_key);
3202 renderer_resources
3203 .font_families_map
3204 .insert(font_families_hash, font_family_hash);
3205 resource_updates.push((font_family_hash, add_font_msg));
3206
3207 for font_size in font_sizes {
3209 insert_font_instances!(font_family_hash, font_key, *font_size);
3210 }
3211 }
3212 }
3213 }
3214
3215 resource_updates
3216}
3217
3218#[allow(unused_variables)]
3234pub fn build_add_image_resource_updates(
3235 renderer_resources: &RendererResources,
3236 id_namespace: IdNamespace,
3237 epoch: Epoch,
3238 document_id: &DocumentId,
3239 images_in_dom: &FastBTreeSet<ImageRef>,
3240 insert_into_active_gl_textures: GlStoreImageFn,
3241) -> Vec<(ImageRefHash, AddImageMsg)> {
3242 images_in_dom
3243 .iter()
3244 .filter_map(|image_ref| {
3245 let image_ref_hash = image_ref_get_hash(image_ref);
3246
3247 if renderer_resources
3248 .currently_registered_images
3249 .contains_key(&image_ref_hash)
3250 {
3251 return None;
3252 }
3253
3254 match image_ref.get_data() {
3257 DecodedImage::Gl(texture) => {
3258 let descriptor = texture.get_descriptor();
3259 let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
3260 let external_image_id = image_ref_hash_to_external_image_id(image_ref_hash);
3264 (insert_into_active_gl_textures)(
3266 *document_id,
3267 epoch,
3268 texture.clone(),
3269 external_image_id,
3270 );
3271 Some((
3272 image_ref_hash,
3273 AddImageMsg(AddImage {
3274 key,
3275 data: ImageData::External(ExternalImageData {
3276 id: external_image_id,
3277 channel_index: 0,
3278 image_type: ExternalImageType::TextureHandle(
3279 ImageBufferKind::Texture2D,
3280 ),
3281 }),
3282 descriptor,
3283 tiling: None,
3284 }),
3285 ))
3286 }
3287 DecodedImage::Raw((descriptor, data)) => {
3288 let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
3289 Some((
3290 image_ref_hash,
3291 AddImageMsg(AddImage {
3292 key,
3293 data: data.clone(), descriptor: *descriptor, tiling: None,
3297 }),
3298 ))
3299 }
3300 DecodedImage::NullImage { .. } | DecodedImage::Callback(_) => None,
3303 }
3304 })
3305 .collect()
3306}
3307
3308#[allow(clippy::needless_pass_by_value)] pub fn add_resources(
3315 renderer_resources: &mut RendererResources,
3316 all_resource_updates: &mut Vec<ResourceUpdate>,
3317 add_font_resources: Vec<(StyleFontFamilyHash, AddFontMsg)>,
3318 add_image_resources: Vec<(ImageRefHash, AddImageMsg)>,
3319) {
3320 all_resource_updates.extend(
3321 add_font_resources
3322 .iter()
3323 .map(|(_, f)| f.into_resource_update()),
3324 );
3325 all_resource_updates.extend(
3326 add_image_resources
3327 .iter()
3328 .map(|(_, i)| i.into_resource_update()),
3329 );
3330
3331 for (image_ref_hash, add_image_msg) in &add_image_resources {
3332 renderer_resources.currently_registered_images.insert(
3333 *image_ref_hash,
3334 ResolvedImage {
3335 key: add_image_msg.0.key,
3336 descriptor: add_image_msg.0.descriptor,
3337 },
3338 );
3339 renderer_resources
3342 .image_key_map
3343 .insert(add_image_msg.0.key, *image_ref_hash);
3344 }
3345
3346 for (_, add_font_msg) in add_font_resources {
3347 use self::AddFontMsg::{Font, Instance};
3348 match add_font_msg {
3349 Font(fk, font_family_hash, font_ref) => {
3350 renderer_resources
3351 .currently_registered_fonts
3352 .entry(fk)
3353 .or_insert_with(|| (font_ref.clone(), OrderedMap::default()));
3354
3355 renderer_resources
3357 .font_hash_map
3358 .insert(font_ref.get_hash(), fk);
3359 }
3360 Instance(fi, size) => {
3361 if let Some((_, instances)) = renderer_resources
3362 .currently_registered_fonts
3363 .get_mut(&fi.font_key)
3364 {
3365 instances.insert(size, fi.key);
3366 }
3367 }
3368 }
3369 }
3370}
3371
3372#[cfg(test)]
3373#[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 {
3375 use super::*;
3376
3377 #[test]
3378 fn normalize_u16_maps_full_range() {
3379 assert_eq!(normalize_u16(0), 0);
3381 assert_eq!(normalize_u16(u16::MAX), 255);
3382 let mid = normalize_u16(u16::MAX / 2);
3384 assert!((126..=128).contains(&mid), "midpoint normalized to {mid}");
3385 assert_eq!(normalize_u16(256), 0);
3388 assert_eq!(normalize_u16(257), 1);
3389 }
3390
3391 #[test]
3392 fn load_bgra8_rejects_wrong_length() {
3393 let short = RawImageData::U8(vec![0u8; 4 * 3].into()); assert!(RawImage::load_bgra8(short, 4, true).is_none());
3397
3398 let ok = RawImageData::U8(vec![255u8; 4 * 4].into()); assert!(RawImage::load_bgra8(ok, 4, true).is_some());
3401
3402 let short2 = RawImageData::U8(vec![0u8; 4 * 2].into());
3404 assert!(RawImage::load_bgra8(short2, 4, false).is_none());
3405 }
3406
3407 #[test]
3410 fn imageref_get_data_reads_backing_box() {
3411 let img = ImageRef::null_image(2, 3, RawImageFormat::RGBA8, vec![7, 8]);
3413 match img.get_data() {
3414 DecodedImage::NullImage { width, height, tag, .. } => {
3415 assert_eq!((*width, *height), (2, 3));
3416 assert_eq!(tag.as_slice(), &[7, 8]);
3417 }
3418 _ => panic!("expected NullImage"),
3419 }
3420 }
3421
3422 #[test]
3423 fn imageref_clone_shares_refcount_and_identity() {
3424 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3427 let c = img.clone();
3428 assert_eq!(img, c); assert!(c.into_inner().is_none());
3431 assert!(img.into_inner().is_some());
3433 }
3434
3435 #[test]
3436 fn imageref_deep_copy_has_distinct_identity() {
3437 let img = ImageRef::null_image(4, 4, RawImageFormat::RGBA8, vec![1]);
3440 let d = img.deep_copy();
3441 assert_ne!(img, d);
3442 drop(img);
3443 assert_eq!(d.get_size().width as usize, 4);
3445 }
3446
3447 #[test]
3448 fn imageref_last_drop_frees_once() {
3449 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3452 let c = img.clone();
3453 drop(img);
3454 drop(c);
3455 }
3456
3457 #[test]
3458 fn imageref_get_callback_none_for_non_callback_and_when_shared() {
3459 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
3462 assert!(img.get_image_callback().is_none());
3463 let c = img.clone();
3464 assert!(img.get_image_callback().is_none()); drop(c);
3466 }
3467
3468 #[test]
3469 fn shared_raw_image_data_read_paths() {
3470 let s = SharedRawImageData::new(vec![10u8, 20, 30].into());
3472 assert_eq!(s.as_ref(), &[10, 20, 30]);
3473 assert_eq!(s.len(), 3);
3474 assert!(!s.is_empty());
3475 assert_eq!(unsafe { *s.as_ptr() }, 10);
3476 assert!(SharedRawImageData::new(Vec::<u8>::new().into()).is_empty());
3477 }
3478
3479 #[test]
3480 fn shared_raw_image_data_clone_shares_alloc() {
3481 let s = SharedRawImageData::new(vec![1u8, 2, 3, 4].into());
3484 let c = s.clone();
3485 assert_eq!(s, c); assert_eq!(s.as_ptr(), c.as_ptr());
3487 assert!(c.into_inner().is_none()); let inner = s.into_inner().expect("sole owner extraction");
3489 assert_eq!(inner.as_ref(), &[1, 2, 3, 4]);
3490 }
3491
3492 #[test]
3493 fn shared_raw_image_data_last_drop_frees_once() {
3494 let s = SharedRawImageData::new(vec![0u8; 8].into());
3496 let c = s.clone();
3497 drop(s);
3498 drop(c);
3499 }
3500}
3501
3502#[cfg(test)]
3503#[allow(
3504 clippy::float_cmp,
3505 clippy::items_after_statements,
3506 clippy::redundant_clone,
3507 clippy::cast_possible_truncation,
3508 clippy::cast_precision_loss,
3509 clippy::cast_sign_loss,
3510 clippy::cast_lossless,
3511 clippy::unreadable_literal,
3512 clippy::too_many_lines,
3513 clippy::many_single_char_names,
3514 clippy::similar_names,
3515 unused_qualifications,
3516 unreachable_pub,
3517 private_interfaces
3518)] mod autotest_generated {
3520 use alloc::string::String;
3521
3522 use super::*;
3523
3524 fn dummy_font_ref() -> FontRef {
3533 static DUMMY_FONT_DATA: u8 = 0;
3534 extern "C" fn dummy_destructor(_: *mut core::ffi::c_void) {}
3535 FontRef::new(
3536 core::ptr::addr_of!(DUMMY_FONT_DATA).cast::<core::ffi::c_void>(),
3537 dummy_destructor,
3538 )
3539 }
3540
3541 fn load_font_none(_: &StyleFontFamily, _: &FcFontCache) -> Option<LoadedFontSource> {
3543 None
3544 }
3545
3546 fn parse_font_none(_: LoadedFontSource) -> Option<FontRef> {
3548 None
3549 }
3550
3551 fn store_gl_texture_noop(_: DocumentId, _: Epoch, _: Texture, _: ExternalImageId) {}
3553
3554 fn test_document_id() -> DocumentId {
3555 DocumentId {
3556 namespace_id: IdNamespace(7),
3557 id: 0,
3558 }
3559 }
3560
3561 fn rgba8_image(w: usize, h: usize) -> RawImage {
3563 RawImage {
3564 pixels: RawImageData::U8(vec![0u8; w * h * 4].into()),
3565 width: w,
3566 height: h,
3567 premultiplied_alpha: true,
3568 data_format: RawImageFormat::RGBA8,
3569 tag: Vec::new().into(),
3570 }
3571 }
3572
3573 fn opaque_red() -> ColorU {
3574 ColorU {
3575 r: 255,
3576 g: 0,
3577 b: 0,
3578 a: 255,
3579 }
3580 }
3581
3582 #[test]
3587 fn match_route_valid_minimal_positive_control() {
3588 let m = match_route("/user/:id", "/user/42").expect("documented example must match");
3590 assert_eq!(m.pattern.as_str(), "/user/:id");
3591 assert_eq!(m.get_param("id").map(AzString::as_str), Some("42"));
3592
3593 let root = match_route("/", "/").expect("root must match root");
3594 assert!(root.params.as_ref().is_empty());
3595
3596 assert!(match_route("/about", "/settings").is_none());
3597 }
3598
3599 #[test]
3600 fn match_route_empty_input_does_not_panic() {
3601 let m = match_route("", "").expect("empty vs empty is a zero-segment match");
3604 assert!(m.params.as_ref().is_empty());
3605 assert!(match_route("", "/").is_some()); assert!(match_route("/", "").is_some());
3607 assert!(match_route("", "/a").is_none()); assert!(match_route("/a", "").is_none());
3609 }
3610
3611 #[test]
3612 fn match_route_whitespace_only_is_not_trimmed() {
3613 assert!(match_route(" ", " ").is_some());
3616 assert!(match_route(" ", "\t\n").is_none());
3617 assert!(match_route("/ ", "/").is_none()); assert!(match_route("/\t\n", "/\t\n").is_some());
3619 }
3620
3621 #[test]
3622 fn match_route_garbage_never_panics() {
3623 for pat in [
3624 "\0\0\0",
3625 "///////",
3626 "::::",
3627 ":",
3628 "%%%$#@!",
3629 "\u{feff}",
3630 "/a/../../etc/passwd",
3631 ] {
3632 for path in ["", "/", "\0", "/a/b/c", "%%%$#@!", "\u{feff}"] {
3633 let _ = match_route(pat, path);
3635 }
3636 }
3637 assert!(match_route("///////", "/").is_some());
3639 let m = match_route("/:", "/hello").expect("empty param name still matches");
3641 assert_eq!(m.get_param("").map(AzString::as_str), Some("hello"));
3642 }
3643
3644 #[test]
3645 fn match_route_leading_trailing_junk_is_rejected_or_ignored() {
3646 assert!(match_route("/user/:id/", "/user/42").is_some());
3649 assert!(match_route("/user/:id", "/user/42/").is_some());
3650 assert!(match_route(" /about ", "/about").is_none());
3652 assert!(match_route("/about", "/about;garbage").is_none());
3653 }
3654
3655 #[test]
3656 fn match_route_boundary_number_strings_are_opaque_segments() {
3657 for v in [
3659 "0",
3660 "-0",
3661 "9223372036854775807",
3662 "-9223372036854775808",
3663 "1e400",
3664 "NaN",
3665 "inf",
3666 "-inf",
3667 "0.0000000000000000001",
3668 ] {
3669 let path = String::from("/user/") + v;
3670 let m = match_route("/user/:id", &path).expect("any segment matches a :param");
3671 assert_eq!(m.get_param("id").map(AzString::as_str), Some(v));
3672 }
3673 }
3674
3675 #[test]
3676 fn match_route_unicode_multibyte_does_not_panic() {
3677 let m = match_route("/user/:id", "/user/\u{1F600}").expect("emoji segment matches");
3679 assert_eq!(m.get_param("id").map(AzString::as_str), Some("\u{1F600}"));
3680
3681 let m = match_route("/:é\u{0301}", "/e\u{0301}\u{202E}x").expect("unicode param name");
3683 assert_eq!(
3684 m.get_param("é\u{0301}").map(AzString::as_str),
3685 Some("e\u{0301}\u{202E}x")
3686 );
3687 assert!(match_route("/é", "/e\u{0301}").is_none());
3689 }
3690
3691 #[test]
3692 fn match_route_extremely_long_input_does_not_hang() {
3693 let huge = String::from("/") + &"a".repeat(1_000_000);
3695 assert!(match_route("/x", &huge).is_none());
3696 let m = match_route("/:id", &huge).expect("one long segment is still one segment");
3697 assert_eq!(m.get_param("id").map(|s| s.as_str().len()), Some(1_000_000));
3698 }
3699
3700 #[test]
3701 fn match_route_deeply_nested_input_does_not_stack_overflow() {
3702 let deep = "/a".repeat(10_000);
3704 let m = match_route(&deep, &deep).expect("identical deep paths match");
3705 assert!(m.params.as_ref().is_empty());
3706
3707 let all_params = "/:p".repeat(10_000);
3708 let m = match_route(&all_params, &deep).expect("10k params extract");
3709 assert_eq!(m.params.as_ref().len(), 10_000);
3710 assert_eq!(m.get_param("p").map(AzString::as_str), Some("a"));
3712
3713 let brackets = String::from("/") + &"[".repeat(10_000);
3714 assert!(match_route("/:x", &brackets).is_some());
3715 }
3716
3717 #[test]
3718 fn match_route_segment_count_mismatch_is_none() {
3719 assert!(match_route("/a/:b", "/a").is_none());
3720 assert!(match_route("/a", "/a/b").is_none());
3721 assert!(match_route("/:a/:b/:c", "/1/2").is_none());
3722 }
3723
3724 #[test]
3725 fn route_match_get_param_missing_keys_return_none() {
3726 let empty = RouteMatch {
3727 pattern: AzString::from_const_str("/"),
3728 params: StringPairVec::from_vec(Vec::new()),
3729 };
3730 assert!(empty.get_param("").is_none());
3732 assert!(empty.get_param(" ").is_none());
3733 assert!(empty.get_param("\t\n").is_none());
3734 assert!(empty.get_param("\u{1F600}").is_none());
3735 assert!(empty.get_param("\0").is_none());
3736 assert!(empty.get_param(&"k".repeat(100_000)).is_none());
3737
3738 let m = match_route("/u/:id", "/u/7").expect("valid");
3740 assert_eq!(m.get_param("id").map(AzString::as_str), Some("7"));
3741 assert!(m.get_param("ID").is_none()); assert!(m.get_param("i").is_none()); assert!(m.get_param(":id").is_none()); }
3745
3746 #[test]
3747 fn app_config_match_route_for_path_adversarial_inputs() {
3748 let mut config = AppConfig::create();
3749 let cb: crate::callbacks::LayoutCallbackType = autotest_layout;
3750 extern "C" fn autotest_layout(
3751 _: RefAny,
3752 _: crate::callbacks::LayoutCallbackInfo,
3753 ) -> crate::dom::Dom {
3754 crate::dom::Dom::create_body()
3755 }
3756 config.add_route(AzString::from_const_str("/"), cb);
3757 config.add_route(AzString::from_const_str("/user/:id"), cb);
3758
3759 let (route, m) = config
3761 .match_route_for_path("/user/42")
3762 .expect("registered route must match");
3763 assert_eq!(route.pattern.as_str(), "/user/:id");
3764 assert_eq!(m.get_param("id").map(AzString::as_str), Some("42"));
3765
3766 assert!(config.match_route_for_path("").is_some());
3768 assert!(config.match_route_for_path("/").is_some());
3769
3770 assert!(config.match_route_for_path("/nope/nope/nope").is_none());
3772 assert!(config.match_route_for_path("\0\0").is_none());
3773 assert!(config.match_route_for_path(" ").is_none());
3774 let long = String::from("/user/") + &"9".repeat(500_000);
3775 assert!(config.match_route_for_path(&long).is_some());
3776 let m = config
3777 .match_route_for_path("/user/\u{1F600}")
3778 .expect("unicode param");
3779 assert_eq!(m.1.get_param("id").map(AzString::as_str), Some("\u{1F600}"));
3780 }
3781
3782 #[test]
3783 fn app_config_add_route_replaces_same_pattern_and_orders_by_insertion() {
3784 extern "C" fn layout_a(_: RefAny, _: crate::callbacks::LayoutCallbackInfo) -> crate::dom::Dom {
3785 crate::dom::Dom::create_body()
3786 }
3787 let cb: crate::callbacks::LayoutCallbackType = layout_a;
3788
3789 let mut config = AppConfig::create();
3790 assert!(config.routes.as_ref().is_empty());
3791 config.add_route(AzString::from_const_str("/dup"), cb);
3792 config.add_route(AzString::from_const_str("/dup"), cb);
3793 assert_eq!(config.routes.as_ref().len(), 1, "same pattern must replace");
3794
3795 let mut config = AppConfig::create();
3797 config.add_route(AzString::from_const_str("/:anything"), cb);
3798 config.add_route(AzString::from_const_str("/about"), cb);
3799 let (route, _) = config.match_route_for_path("/about").expect("matches");
3800 assert_eq!(route.pattern.as_str(), "/:anything");
3801 }
3802
3803 #[test]
3808 fn dpi_scale_factor_new_handles_nan_and_infinities() {
3809 assert_eq!(DpiScaleFactor::new(0.0).inner.get(), 0.0);
3811 assert_eq!(DpiScaleFactor::new(1.0).inner.get(), 1.0);
3812 assert_eq!(DpiScaleFactor::new(f32::NAN).inner.get(), 0.0);
3813 assert!(DpiScaleFactor::new(f32::INFINITY).inner.get().is_finite());
3814 assert!(DpiScaleFactor::new(f32::NEG_INFINITY).inner.get().is_finite());
3815 assert!(DpiScaleFactor::new(f32::MAX).inner.get().is_finite());
3816 assert!(DpiScaleFactor::new(f32::MIN).inner.get().is_finite());
3817 assert_eq!(DpiScaleFactor::new(f32::MIN_POSITIVE).inner.get(), 0.0);
3819 assert_eq!(DpiScaleFactor::new(1.5), DpiScaleFactor::new(1.5));
3821 assert_ne!(DpiScaleFactor::new(1.5), DpiScaleFactor::new(2.0));
3822 }
3823
3824 #[test]
3825 fn named_font_new_keeps_fields_verbatim() {
3826 let f = NamedFont::new(
3827 AzString::from_const_str(""),
3828 U8Vec::from_vec(Vec::new()),
3829 );
3830 assert_eq!(f.name.as_str(), "");
3831 assert!(f.bytes.as_ref().is_empty());
3832
3833 let bytes = vec![0u8, 255, 128];
3834 let f = NamedFont::new(AzString::from(String::from("\u{1F600}")), bytes.clone().into());
3835 assert_eq!(f.name.as_str(), "\u{1F600}");
3836 assert_eq!(f.bytes.as_ref(), bytes.as_slice());
3837 }
3838
3839 #[test]
3840 fn loaded_font_new_keeps_fields_verbatim_at_limits() {
3841 let f = LoadedFont::new(0, AzString::from_const_str(""), 0, false);
3842 assert_eq!(f.font_hash, 0);
3843 assert_eq!(f.num_glyphs, 0);
3844 assert!(!f.has_bytes);
3845
3846 let f = LoadedFont::new(u64::MAX, AzString::from_const_str("x"), u32::MAX, true);
3847 assert_eq!(f.font_hash, u64::MAX);
3848 assert_eq!(f.num_glyphs, u32::MAX);
3849 assert!(f.has_bytes);
3850 }
3851
3852 #[test]
3853 fn brush_new_defaults_and_extreme_radius() {
3854 let b = Brush::new(opaque_red(), 4.0);
3855 assert_eq!(b.radius, 4.0);
3856 assert_eq!(b.hardness, 0.5);
3857 assert_eq!(b.flow, 1.0);
3858 assert_eq!(b.spacing, 0.25);
3859 assert_eq!(b.color, opaque_red());
3860
3861 assert!(Brush::new(opaque_red(), f32::NAN).radius.is_nan());
3863 assert_eq!(Brush::new(opaque_red(), -0.0).radius, -0.0);
3864 assert_eq!(Brush::new(opaque_red(), f32::INFINITY).radius, f32::INFINITY);
3865 }
3866
3867 #[test]
3868 fn image_cache_new_is_empty_and_default_is_neutral() {
3869 let cache = ImageCache::new();
3870 assert!(cache.image_id_map.is_empty());
3871 assert!(ImageCache::default().image_id_map.is_empty());
3872 }
3873
3874 #[test]
3875 fn gl_texture_cache_empty_is_neutral() {
3876 let cache = GlTextureCache::empty();
3877 assert!(cache.solved_textures.is_empty());
3878 assert!(cache.hashes.is_empty());
3879 let d = GlTextureCache::default();
3880 assert!(d.solved_textures.is_empty());
3881 assert!(d.hashes.is_empty());
3882 }
3883
3884 #[test]
3885 fn external_image_id_new_is_monotonic() {
3886 let a = ExternalImageId::new();
3887 let b = ExternalImageId::new();
3888 assert!(b.inner > a.inner, "the counter must strictly increase");
3889 assert!(ExternalImageId::default().inner > b.inner);
3890 }
3891
3892 #[test]
3893 fn shared_raw_image_data_new_invariants() {
3894 let empty = SharedRawImageData::new(U8Vec::from_vec(Vec::new()));
3895 assert_eq!(empty.len(), 0);
3896 assert!(empty.is_empty());
3897 assert!(empty.as_ref().is_empty());
3898 assert!(empty.get_bytes().is_empty());
3899 assert!(!empty.as_ptr().is_null());
3901
3902 let big = SharedRawImageData::new(vec![7u8; 100_000].into());
3903 assert_eq!(big.len(), 100_000);
3904 assert!(!big.is_empty());
3905 assert_eq!(big.as_ref().len(), big.len());
3906 assert_eq!(big.get_bytes(), big.as_ref());
3907 assert_eq!(big.into_inner().expect("sole owner").as_ref().len(), 100_000);
3909 }
3910
3911 #[test]
3912 fn app_config_create_registers_builtins_and_defaults() {
3913 let config = AppConfig::create();
3914 assert_eq!(config.log_level, AppLogLevel::Error);
3915 assert!(!config.enable_visual_panic_hook);
3916 assert!(config.enable_logging_on_panic);
3917 assert_eq!(config.termination_behavior, AppTerminationBehavior::EndProcess);
3918 assert!(config.routes.as_ref().is_empty());
3919 assert!(matches!(
3920 config.mock_css_environment,
3921 OptionCssMockEnvironment::None
3922 ));
3923 let libs = config.component_libraries.as_ref();
3925 assert_eq!(libs.len(), 1);
3926 assert_eq!(libs[0].name.as_str(), "builtin");
3927 assert!(!libs[0].components.as_ref().is_empty());
3928 }
3929
3930 #[test]
3931 fn app_config_add_component_library_replaces_same_name() {
3932 let register: crate::xml::RegisterComponentLibraryFnType =
3933 crate::xml::register_builtin_components;
3934 let mut config = AppConfig::create();
3935 let n_builtin = config.component_libraries.as_ref()[0].components.as_ref().len();
3936
3937 config.add_component_library(AzString::from_const_str("builtin"), register);
3939 assert_eq!(config.component_libraries.as_ref().len(), 1);
3940 assert_eq!(
3941 config.component_libraries.as_ref()[0].components.as_ref().len(),
3942 n_builtin
3943 );
3944
3945 config.add_component_library(AzString::from_const_str(""), register);
3947 config.add_component_library(AzString::from_const_str("\u{1F600}"), register);
3948 assert_eq!(config.component_libraries.as_ref().len(), 3);
3949 assert_eq!(config.component_libraries.as_ref()[2].name.as_str(), "\u{1F600}");
3950 }
3951
3952 #[test]
3953 fn app_config_with_mock_environment_sets_the_option() {
3954 let config = AppConfig::create().with_mock_environment(CssMockEnvironment::dark_theme());
3955 match config.mock_css_environment {
3956 OptionCssMockEnvironment::Some(env) => {
3957 assert!(matches!(
3958 env.theme,
3959 azul_css::dynamic_selector::OptionThemeCondition::Some(
3960 azul_css::dynamic_selector::ThemeCondition::Dark
3961 )
3962 ));
3963 }
3964 OptionCssMockEnvironment::None => panic!("mock env must be Some"),
3965 }
3966 let config = AppConfig::create()
3968 .with_mock_environment(CssMockEnvironment::linux())
3969 .with_mock_environment(CssMockEnvironment::windows());
3970 match config.mock_css_environment {
3971 OptionCssMockEnvironment::Some(env) => assert!(matches!(
3972 env.os,
3973 azul_css::dynamic_selector::OptionOsCondition::Some(
3974 azul_css::dynamic_selector::OsCondition::Windows
3975 )
3976 )),
3977 OptionCssMockEnvironment::None => panic!("mock env must be Some"),
3978 }
3979 }
3980
3981 #[test]
3986 fn css_mock_environment_presets_only_set_their_own_field() {
3987 use azul_css::dynamic_selector::{
3988 OptionOsCondition, OptionThemeCondition, OsCondition, ThemeCondition,
3989 };
3990
3991 for (mock, os) in [
3992 (CssMockEnvironment::linux(), OsCondition::Linux),
3993 (CssMockEnvironment::windows(), OsCondition::Windows),
3994 (CssMockEnvironment::macos(), OsCondition::MacOS),
3995 ] {
3996 assert!(matches!(mock.os, OptionOsCondition::Some(o) if o == os));
3997 assert!(matches!(mock.theme, OptionThemeCondition::None));
3999 assert!(matches!(mock.viewport_width, azul_css::OptionF32::None));
4000 }
4001
4002 assert!(matches!(
4003 CssMockEnvironment::dark_theme().theme,
4004 OptionThemeCondition::Some(ThemeCondition::Dark)
4005 ));
4006 assert!(matches!(
4007 CssMockEnvironment::light_theme().theme,
4008 OptionThemeCondition::Some(ThemeCondition::Light)
4009 ));
4010 assert!(matches!(
4011 CssMockEnvironment::dark_theme().os,
4012 OptionOsCondition::None
4013 ));
4014 }
4015
4016 #[test]
4017 fn css_mock_environment_apply_to_overrides_only_set_fields() {
4018 use azul_css::dynamic_selector::{
4019 BoolCondition, DynamicSelectorContext, OptionOsCondition, OptionThemeCondition,
4020 OsCondition, ThemeCondition,
4021 };
4022
4023 let mut ctx = DynamicSelectorContext::default();
4025 let before_os = ctx.os;
4026 let before_lang = ctx.language.clone();
4027 let before_w = ctx.viewport_width;
4028 CssMockEnvironment::default().apply_to(&mut ctx);
4029 assert_eq!(ctx.os, before_os);
4030 assert_eq!(ctx.language.as_str(), before_lang.as_str());
4031 assert_eq!(ctx.viewport_width, before_w);
4032
4033 let mock = CssMockEnvironment {
4036 os: OptionOsCondition::Some(OsCondition::Windows),
4037 theme: OptionThemeCondition::Some(ThemeCondition::Dark),
4038 language: azul_css::OptionString::Some(AzString::from_const_str("de-DE")),
4039 viewport_width: azul_css::OptionF32::Some(f32::NAN),
4040 viewport_height: azul_css::OptionF32::Some(f32::INFINITY),
4041 prefers_reduced_motion: azul_css::OptionBool::Some(true),
4042 prefers_high_contrast: azul_css::OptionBool::Some(false),
4043 ..Default::default()
4044 };
4045 let mut ctx = DynamicSelectorContext::default();
4046 mock.apply_to(&mut ctx);
4047 assert_eq!(ctx.os, OsCondition::Windows);
4048 assert_eq!(ctx.theme, ThemeCondition::Dark);
4049 assert_eq!(ctx.language.as_str(), "de-DE");
4050 assert!(ctx.viewport_width.is_nan());
4051 assert_eq!(ctx.viewport_height, f32::INFINITY);
4052 assert_eq!(ctx.prefers_reduced_motion, BoolCondition::True);
4053 assert_eq!(ctx.prefers_high_contrast, BoolCondition::False);
4054
4055 let mut ctx2 = ctx.clone();
4057 mock.apply_to(&mut ctx2);
4058 assert_eq!(ctx2.os, ctx.os);
4059 assert_eq!(ctx2.theme, ctx.theme);
4060 }
4061
4062 #[test]
4067 fn brush_dab_coverage_boundaries_and_monotonicity() {
4068 assert_eq!(brush_dab_coverage(0.0, 0.5), 1.0);
4070 assert_eq!(brush_dab_coverage(1.0, 0.5), 0.0);
4071 assert_eq!(brush_dab_coverage(-5.0, 0.5), 1.0);
4073 assert_eq!(brush_dab_coverage(2.0, 0.5), 0.0);
4074 assert_eq!(brush_dab_coverage(f32::INFINITY, 0.5), 0.0);
4075 assert_eq!(brush_dab_coverage(f32::NEG_INFINITY, 0.5), 1.0);
4076
4077 let mut prev = f32::INFINITY;
4079 for i in 0..=100 {
4080 let t = i as f32 / 100.0;
4081 let c = brush_dab_coverage(t, 0.5);
4082 assert!((0.0..=1.0).contains(&c), "coverage {c} out of range at t={t}");
4083 assert!(c <= prev + 1.0e-6, "not monotonic at t={t}");
4084 prev = c;
4085 }
4086 }
4087
4088 #[test]
4089 fn brush_dab_coverage_hardness_limits_never_divide_by_zero() {
4090 assert_eq!(brush_dab_coverage(0.5, 1.0), 1.0);
4093 assert!(brush_dab_coverage(1.0, 1.0).is_finite());
4094 assert_eq!(brush_dab_coverage(1.0, 1.0), 1.0); assert_eq!(brush_dab_coverage(2.0, 1.0), 0.0);
4096
4097 assert_eq!(brush_dab_coverage(0.5, -10.0), brush_dab_coverage(0.5, 0.0));
4099 assert_eq!(brush_dab_coverage(0.5, 10.0), brush_dab_coverage(0.5, 1.0));
4100 assert_eq!(
4101 brush_dab_coverage(0.5, f32::NEG_INFINITY),
4102 brush_dab_coverage(0.5, 0.0)
4103 );
4104 assert!(brush_dab_coverage(0.5, f32::INFINITY).is_finite());
4105 }
4106
4107 #[test]
4108 fn brush_dab_coverage_nan_propagates_without_panicking() {
4109 assert!(brush_dab_coverage(f32::NAN, 0.5).is_nan());
4112 assert!(brush_dab_coverage(0.5, f32::NAN).is_nan());
4113 assert!(brush_dab_coverage(f32::NAN, f32::NAN).is_nan());
4114 }
4115
4116 #[test]
4117 fn normalize_u16_is_monotonic_and_saturating() {
4118 assert_eq!(normalize_u16(u16::MIN), 0);
4119 assert_eq!(normalize_u16(u16::MAX), u8::MAX);
4120 let mut prev = 0u8;
4121 for i in (0..=u16::MAX).step_by(97) {
4122 let v = normalize_u16(i);
4123 assert!(v >= prev, "normalize_u16 must be monotonic ({i} -> {v})");
4124 prev = v;
4125 }
4126 }
4127
4128 #[test]
4129 fn premultiply_alpha_ignores_non_4_byte_slices() {
4130 for len in [0usize, 1, 2, 3, 5, 8] {
4132 let mut buf = vec![200u8; len];
4133 let before = buf.clone();
4134 premultiply_alpha(&mut buf);
4135 assert_eq!(buf, before, "len {len} must be left untouched");
4136 }
4137 }
4138
4139 #[test]
4140 fn premultiply_alpha_boundary_values_never_overflow() {
4141 let mut opaque = [255u8, 128, 0, 255];
4143 premultiply_alpha(&mut opaque);
4144 assert_eq!(opaque, [255, 128, 0, 255]);
4145
4146 let mut transparent = [255u8, 255, 255, 0];
4148 premultiply_alpha(&mut transparent);
4149 assert_eq!(transparent, [0, 0, 0, 0]);
4150
4151 let mut half = [255u8, 128, 0, 128];
4153 premultiply_alpha(&mut half);
4154 assert_eq!(half, [128, 64, 0, 128]);
4155
4156 let mut max = [255u8, 255, 255, 255];
4158 premultiply_alpha(&mut max);
4159 assert_eq!(max, [255, 255, 255, 255]);
4160 }
4161
4162 #[test]
4163 fn au_from_px_saturates_at_limits_and_nan() {
4164 assert_eq!(Au::from_px(0.0).0, 0);
4165 assert_eq!(Au::from_px(-0.0).0, 0);
4166 assert_eq!(Au::from_px(1.0).0, AU_PER_PX);
4167 assert_eq!(Au::from_px(-1.0).0, -AU_PER_PX);
4168 assert_eq!(Au::from_px(f32::NAN).0, 0);
4170 assert_eq!(Au::from_px(f32::INFINITY).0, MAX_AU);
4172 assert_eq!(Au::from_px(f32::NEG_INFINITY).0, MIN_AU);
4173 assert_eq!(Au::from_px(f32::MAX).0, MAX_AU);
4174 assert_eq!(Au::from_px(f32::MIN).0, MIN_AU);
4175 for px in [-1.0e9_f32, -1.0, 0.5, 16.0, 1.0e9] {
4177 let au = Au::from_px(px).0;
4178 assert!((MIN_AU..=MAX_AU).contains(&au), "{px} -> {au} escaped the clamp");
4179 }
4180 }
4181
4182 #[test]
4183 fn au_px_round_trip_is_stable() {
4184 for px in [0.0_f32, 0.5, 1.0, 12.0, 16.0, 72.5, -3.25, 1000.0] {
4186 let back = Au::from_px(px).into_px();
4187 assert!(
4188 (back - px).abs() <= 1.0 / AU_PER_PX as f32,
4189 "{px} round-tripped to {back}"
4190 );
4191 }
4192 assert_eq!(Au::from_px(16.0).into_px(), 16.0);
4194 assert!(Au(MAX_AU).into_px().is_finite());
4196 assert!(Au(MIN_AU).into_px().is_finite());
4197 assert!(Au(i32::MIN).into_px().is_finite());
4198 assert!(Au(i32::MAX).into_px().is_finite());
4199 }
4200
4201 #[test]
4202 fn font_size_to_au_zero_negative_and_typical() {
4203 use azul_css::props::basic::PixelValue;
4204 let au = |px: isize| {
4205 font_size_to_au(StyleFontSize {
4206 inner: PixelValue::const_px(px),
4207 })
4208 .0
4209 };
4210 assert_eq!(au(0), 0);
4211 assert_eq!(au(16), 16 * AU_PER_PX);
4212 assert_eq!(au(-10), -10 * AU_PER_PX);
4213 assert!((MIN_AU..=MAX_AU).contains(&au(1_000_000)));
4215 assert!((MIN_AU..=MAX_AU).contains(&au(-1_000_000)));
4216 }
4217
4218 #[test]
4223 fn epoch_new_from_and_into_u32() {
4224 assert_eq!(Epoch::new().into_u32(), 0);
4225 assert_eq!(Epoch::default().into_u32(), 0);
4226 assert_eq!(Epoch::from(0).into_u32(), 0);
4227 assert_eq!(Epoch::from(1).into_u32(), 1);
4228 assert_eq!(Epoch::from(u32::MAX).into_u32(), u32::MAX);
4229 assert_eq!(Epoch::from(u32::MAX - 1).into_u32(), u32::MAX - 1);
4230 }
4231
4232 #[test]
4233 fn epoch_increment_wraps_at_max_minus_one_and_never_reaches_max() {
4234 let mut e = Epoch::new();
4235 e.increment();
4236 assert_eq!(e.into_u32(), 1);
4237
4238 let mut e = Epoch::from(u32::MAX - 1);
4240 e.increment();
4241 assert_eq!(e.into_u32(), 0, "MAX-1 must wrap to 0, never to u32::MAX");
4242
4243 let mut e = Epoch::from(u32::MAX);
4246 e.increment();
4247 assert_eq!(e.into_u32(), u32::MAX);
4248
4249 let mut e = Epoch::from(u32::MAX - 3);
4251 for _ in 0..8 {
4252 e.increment();
4253 assert_ne!(e.into_u32(), u32::MAX);
4254 }
4255 }
4256
4257 #[test]
4258 fn epoch_display_is_non_empty_for_edge_values() {
4259 assert_eq!(alloc::format!("{}", Epoch::new()), "0");
4260 assert_eq!(alloc::format!("{}", Epoch::from(42)), "42");
4261 assert_eq!(
4262 alloc::format!("{}", Epoch::from(u32::MAX)),
4263 alloc::format!("{}", u32::MAX)
4264 );
4265 assert!(!alloc::format!("{:?}", Epoch::default()).is_empty());
4266 }
4267
4268 #[test]
4269 fn id_namespace_display_and_debug_are_well_formed() {
4270 assert_eq!(alloc::format!("{}", IdNamespace(0)), "IdNamespace(0)");
4271 assert_eq!(
4272 alloc::format!("{}", IdNamespace(u32::MAX)),
4273 alloc::format!("IdNamespace({})", u32::MAX)
4274 );
4275 assert_eq!(
4277 alloc::format!("{:?}", IdNamespace(7)),
4278 alloc::format!("{}", IdNamespace(7))
4279 );
4280 }
4281
4282 #[test]
4287 fn unique_keys_are_strictly_increasing_and_keep_their_namespace() {
4288 let ns = IdNamespace(u32::MAX);
4289
4290 let a = ImageKey::unique(ns);
4291 let b = ImageKey::unique(ns);
4292 assert_eq!(a.namespace, ns);
4293 assert!(b.key > a.key, "ImageKey counter must strictly increase");
4294 assert_eq!(ImageKey::DUMMY.key, 0);
4296 assert_ne!(a, ImageKey::DUMMY);
4297
4298 let a = FontKey::unique(ns);
4299 let b = FontKey::unique(ns);
4300 assert_eq!(a.namespace, ns);
4301 assert!(b.key > a.key);
4302
4303 let a = FontInstanceKey::unique(IdNamespace(0));
4304 let b = FontInstanceKey::unique(IdNamespace(0));
4305 assert_eq!(a.namespace, IdNamespace(0));
4306 assert!(b.key > a.key);
4307 }
4308
4309 #[test]
4310 fn image_ref_id_counter_is_monotonic_and_never_zero() {
4311 let a = next_image_ref_id();
4313 let b = next_image_ref_id();
4314 assert!(a > 0 && b > a);
4315 }
4316
4317 #[test]
4318 fn image_ref_hash_conversions_are_lossless_and_agree() {
4319 let img = ImageRef::null_image(1, 1, RawImageFormat::RGBA8, Vec::new());
4320 let hash = img.get_hash();
4321 assert_eq!(hash, image_ref_get_hash(&img));
4322
4323 let key = image_ref_hash_to_image_key(hash, IdNamespace(9));
4324 assert_eq!(key.namespace, IdNamespace(9));
4325 assert_eq!(key.key, hash.inner, "the u64 id must survive verbatim");
4326
4327 let ext = image_ref_hash_to_external_image_id(hash);
4328 assert_eq!(ext.inner, hash.inner);
4329
4330 for inner in [0u64, 1, u64::MAX, u64::MAX - 1] {
4332 let h = ImageRefHash { inner };
4333 assert_eq!(image_ref_hash_to_image_key(h, IdNamespace(0)).key, inner);
4334 assert_eq!(image_ref_hash_to_external_image_id(h).inner, inner);
4335 }
4336 }
4337
4338 #[test]
4339 fn texture_external_image_id_is_deterministic_and_collision_free() {
4340 let id = |d: usize, n: usize| texture_external_image_id(DomId { inner: d }, NodeId::new(n));
4341
4342 assert_eq!(id(3, 7), id(3, 7));
4344 assert_eq!(id(0, 0).inner, 0);
4345 assert_eq!(id(1, 2).inner, (1u64 << 32) | 2);
4347 assert_ne!(id(0, 1), id(1, 0));
4349 assert_eq!(id(0, u32::MAX as usize).inner, u64::from(u32::MAX));
4351 assert_eq!(
4352 id(u32::MAX as usize, 0).inner,
4353 u64::from(u32::MAX) << 32
4354 );
4355 }
4356
4357 #[test]
4362 fn raw_image_data_typed_getters_only_match_their_own_variant() {
4363 let u8v = RawImageData::U8(vec![1u8, 2].into());
4364 let u16v = RawImageData::U16(vec![1u16, 2].into());
4365 let f32v = RawImageData::F32(vec![1.0f32, 2.0].into());
4366
4367 assert_eq!(u8v.get_u8_vec_ref().map(|v| v.len()), Some(2));
4368 assert!(u8v.get_u16_vec_ref().is_none());
4369 assert!(u8v.get_f32_vec_ref().is_none());
4370
4371 assert!(u16v.get_u8_vec_ref().is_none());
4372 assert_eq!(u16v.get_u16_vec_ref().map(|v| v.len()), Some(2));
4373 assert!(u16v.get_f32_vec_ref().is_none());
4374
4375 assert!(f32v.get_u8_vec_ref().is_none());
4376 assert!(f32v.get_u16_vec_ref().is_none());
4377 assert_eq!(f32v.get_f32_vec_ref().map(|v| v.len()), Some(2));
4378
4379 let empty = RawImageData::U8(U8Vec::from_vec(Vec::new()));
4381 assert_eq!(empty.get_u8_vec_ref().map(|v| v.len()), Some(0));
4382
4383 assert!(RawImageData::U8(vec![9u8].into()).get_u8_vec().is_some());
4385 assert!(RawImageData::U16(vec![9u16].into()).get_u8_vec().is_none());
4386 assert!(RawImageData::U16(vec![9u16].into()).get_u16_vec().is_some());
4387 assert!(RawImageData::F32(vec![9.0f32].into()).get_u16_vec().is_none());
4388 }
4389
4390 #[test]
4395 fn load_fns_reject_wrong_payload_type() {
4396 let u16_1px = || RawImageData::U16(vec![0u16; 4].into());
4399 let f32_1px = || RawImageData::F32(vec![0.0f32; 4].into());
4400 let u8_1px = || RawImageData::U8(vec![0u8; 4].into());
4401
4402 assert!(RawImage::load_r8(u16_1px(), 4).is_none());
4403 assert!(RawImage::load_rg8(f32_1px(), 2, true).is_none());
4404 assert!(RawImage::load_rgb8(u16_1px(), 1).is_none());
4405 assert!(RawImage::load_rgba8(f32_1px(), 1, true).is_none());
4406 assert!(RawImage::load_r16(u8_1px(), 4).is_none());
4407 assert!(RawImage::load_rg16(f32_1px(), 2).is_none());
4408 assert!(RawImage::load_rgb16(u8_1px(), 1).is_none());
4409 assert!(RawImage::load_rgba16(u8_1px(), 1, true).is_none());
4410 assert!(RawImage::load_bgr8(u16_1px(), 1).is_none());
4411 assert!(RawImage::load_bgra8(u16_1px(), 1, true).is_none());
4412 assert!(RawImage::load_rgbf32(u8_1px(), 1).is_none());
4413 assert!(RawImage::load_rgbaf32(u16_1px(), 1, true).is_none());
4414 }
4415
4416 #[test]
4417 fn load_fns_reject_every_wrong_length() {
4418 assert!(RawImage::load_r8(RawImageData::U8(vec![0u8; 3].into()), 4).is_none());
4420 assert!(RawImage::load_r8(RawImageData::U8(vec![0u8; 5].into()), 4).is_none());
4421 assert!(RawImage::load_rg8(RawImageData::U8(vec![0u8; 3].into()), 2, true).is_none());
4422 assert!(RawImage::load_rg8(RawImageData::U8(vec![0u8; 5].into()), 2, true).is_none());
4423 assert!(RawImage::load_rgb8(RawImageData::U8(vec![0u8; 5].into()), 2).is_none());
4424 assert!(RawImage::load_rgb8(RawImageData::U8(vec![0u8; 7].into()), 2).is_none());
4425 assert!(RawImage::load_rgba8(RawImageData::U8(vec![0u8; 7].into()), 2, true).is_none());
4426 assert!(RawImage::load_rgba8(RawImageData::U8(vec![0u8; 9].into()), 2, false).is_none());
4427 assert!(RawImage::load_r16(RawImageData::U16(vec![0u16; 3].into()), 4).is_none());
4428 assert!(RawImage::load_rg16(RawImageData::U16(vec![0u16; 3].into()), 2).is_none());
4429 assert!(RawImage::load_rgb16(RawImageData::U16(vec![0u16; 5].into()), 2).is_none());
4430 assert!(RawImage::load_rgba16(RawImageData::U16(vec![0u16; 7].into()), 2, true).is_none());
4431 assert!(RawImage::load_bgr8(RawImageData::U8(vec![0u8; 5].into()), 2).is_none());
4432 assert!(RawImage::load_bgra8(RawImageData::U8(vec![0u8; 7].into()), 2, false).is_none());
4433 assert!(RawImage::load_rgbf32(RawImageData::F32(vec![0.0f32; 5].into()), 2).is_none());
4434 assert!(
4435 RawImage::load_rgbaf32(RawImageData::F32(vec![0.0f32; 7].into()), 2, true).is_none()
4436 );
4437 }
4438
4439 #[test]
4440 fn load_fns_accept_zero_pixels() {
4441 let empty_u8 = || RawImageData::U8(U8Vec::from_vec(Vec::new()));
4443 let empty_u16 = || RawImageData::U16(U16Vec::from_vec(Vec::new()));
4444 let empty_f32 = || RawImageData::F32(F32Vec::from_vec(Vec::new()));
4445
4446 assert_eq!(RawImage::load_r8(empty_u8(), 0).map(|(b, o)| (b.len(), o)), Some((0, false)));
4447 assert_eq!(RawImage::load_rg8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4448 assert_eq!(RawImage::load_rgb8(empty_u8(), 0).map(|(b, _)| b.len()), Some(0));
4449 assert_eq!(RawImage::load_rgba8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4450 assert_eq!(RawImage::load_r16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4451 assert_eq!(RawImage::load_rg16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4452 assert_eq!(RawImage::load_rgb16(empty_u16(), 0).map(|(b, _)| b.len()), Some(0));
4453 assert_eq!(RawImage::load_rgba16(empty_u16(), 0, true).map(|(b, _)| b.len()), Some(0));
4454 assert_eq!(RawImage::load_bgr8(empty_u8(), 0).map(|(b, _)| b.len()), Some(0));
4455 assert_eq!(RawImage::load_bgra8(empty_u8(), 0, true).map(|(b, _)| b.len()), Some(0));
4456 assert_eq!(RawImage::load_rgbf32(empty_f32(), 0).map(|(b, _)| b.len()), Some(0));
4457 assert_eq!(RawImage::load_rgbaf32(empty_f32(), 0, true).map(|(b, _)| b.len()), Some(0));
4458 }
4459
4460 #[test]
4461 fn load_r8_passes_data_through_and_is_never_opaque() {
4462 let (bytes, is_opaque) =
4464 RawImage::load_r8(RawImageData::U8(vec![0u8, 128, 255, 1].into()), 4)
4465 .expect("exact length");
4466 assert_eq!(bytes.as_ref(), &[0, 128, 255, 1]);
4467 assert!(!is_opaque, "R8 is documented as never opaque");
4468 }
4469
4470 #[test]
4471 fn load_rgb8_and_bgr8_swizzle_to_bgra_opaque() {
4472 let (bytes, is_opaque) =
4474 RawImage::load_rgb8(RawImageData::U8(vec![1u8, 2, 3].into()), 1).expect("1 px");
4475 assert_eq!(bytes.as_ref(), &[3, 2, 1, 255]);
4476 assert!(is_opaque);
4477
4478 let (bytes, is_opaque) =
4480 RawImage::load_bgr8(RawImageData::U8(vec![1u8, 2, 3].into()), 1).expect("1 px");
4481 assert_eq!(bytes.as_ref(), &[1, 2, 3, 255]);
4482 assert!(is_opaque);
4483 }
4484
4485 #[test]
4486 fn load_rgba8_swizzles_and_detects_transparency() {
4487 let (bytes, is_opaque) =
4489 RawImage::load_rgba8(RawImageData::U8(vec![10u8, 20, 30, 255].into()), 1, true)
4490 .expect("1 px");
4491 assert_eq!(bytes.as_ref(), &[30, 20, 10, 255]);
4492 assert!(is_opaque);
4493
4494 let (_, is_opaque) =
4496 RawImage::load_rgba8(RawImageData::U8(vec![0u8, 0, 0, 254].into()), 1, true)
4497 .expect("1 px");
4498 assert!(!is_opaque);
4499
4500 let (bytes, is_opaque) =
4502 RawImage::load_rgba8(RawImageData::U8(vec![10u8, 20, 30, 128].into()), 1, false)
4503 .expect("1 px");
4504 assert_eq!(bytes.as_ref(), &[15, 10, 5, 128]);
4505 assert!(!is_opaque);
4506
4507 let (bytes, _) =
4509 RawImage::load_rgba8(RawImageData::U8(vec![255u8, 255, 255, 0].into()), 1, false)
4510 .expect("1 px");
4511 assert_eq!(bytes.as_ref(), &[0, 0, 0, 0]);
4512 }
4513
4514 #[test]
4515 fn load_rg8_expands_grey_to_bgra() {
4516 let (bytes, is_opaque) =
4518 RawImage::load_rg8(RawImageData::U8(vec![100u8, 255].into()), 1, true).expect("1 px");
4519 assert_eq!(bytes.as_ref(), &[100, 100, 100, 255]);
4520 assert!(is_opaque);
4521
4522 let (bytes, is_opaque) =
4523 RawImage::load_rg8(RawImageData::U8(vec![100u8, 128].into()), 1, false).expect("1 px");
4524 assert_eq!(bytes.as_ref(), &[50, 50, 50, 128]);
4525 assert!(!is_opaque);
4526 }
4527
4528 #[test]
4529 fn load_16_bit_formats_normalize_to_8_bit() {
4530 let (bytes, is_opaque) =
4532 RawImage::load_r16(RawImageData::U16(vec![u16::MAX].into()), 1).expect("1 px");
4533 assert_eq!(bytes.as_ref(), &[255, 255, 255, 255]);
4534 assert!(is_opaque);
4535
4536 let (bytes, is_opaque) =
4537 RawImage::load_rg16(RawImageData::U16(vec![0u16, u16::MAX].into()), 1).expect("1 px");
4538 assert_eq!(bytes.as_ref(), &[0, 0, 0, 255]);
4539 assert!(is_opaque);
4540
4541 let (bytes, _) = RawImage::load_rgb16(
4543 RawImageData::U16(vec![u16::MAX, 0, 0].into()),
4544 1,
4545 )
4546 .expect("1 px");
4547 assert_eq!(bytes.as_ref(), &[0, 0, 255, 255]);
4548
4549 let (bytes, is_opaque) = RawImage::load_rgba16(
4551 RawImageData::U16(vec![u16::MAX, u16::MAX, u16::MAX, 0].into()),
4552 1,
4553 false,
4554 )
4555 .expect("1 px");
4556 assert_eq!(bytes.as_ref(), &[0, 0, 0, 0]);
4557 assert!(!is_opaque);
4558 }
4559
4560 #[test]
4561 fn load_f32_formats_saturate_on_out_of_range_nan_and_inf() {
4562 let (bytes, is_opaque) = RawImage::load_rgbf32(
4565 RawImageData::F32(vec![2.0f32, -1.0, f32::NAN].into()),
4566 1,
4567 )
4568 .expect("1 px");
4569 assert_eq!(bytes.as_ref(), &[0, 0, 255, 255], "b=NaN->0, g=-1->0, r=2.0->255");
4570 assert!(is_opaque);
4571
4572 let (bytes, is_opaque) = RawImage::load_rgbaf32(
4573 RawImageData::F32(vec![f32::INFINITY, f32::NEG_INFINITY, 0.5, 1.0].into()),
4574 1,
4575 true,
4576 )
4577 .expect("1 px");
4578 assert_eq!(bytes.as_ref(), &[127, 0, 255, 255]);
4579 assert!(is_opaque);
4580
4581 let (_, is_opaque) = RawImage::load_rgbaf32(
4583 RawImageData::F32(vec![1.0f32, 1.0, 1.0, f32::NAN].into()),
4584 1,
4585 true,
4586 )
4587 .expect("1 px");
4588 assert!(!is_opaque);
4589 }
4590
4591 #[test]
4596 fn raw_image_null_image_encodes_to_an_empty_bgra8_descriptor() {
4597 let null = RawImage::null_image();
4598 assert_eq!(null.width, 0);
4599 assert_eq!(null.height, 0);
4600 assert_eq!(null.data_format, RawImageFormat::BGRA8);
4601 assert!(null.premultiplied_alpha);
4602
4603 let (data, descriptor) = null
4604 .into_loaded_image_source()
4605 .expect("a 0x0 image is still a valid (empty) source");
4606 assert_eq!(descriptor.width, 0);
4607 assert_eq!(descriptor.height, 0);
4608 assert_eq!(descriptor.format, RawImageFormat::BGRA8);
4609 assert_eq!(descriptor.offset, 0);
4610 match data {
4611 ImageData::Raw(bytes) => assert!(bytes.is_empty()),
4612 ImageData::External(_) => panic!("a RawImage must never encode to External"),
4613 }
4614 }
4615
4616 #[test]
4617 fn raw_image_allocate_mask_zero_and_negative_sizes() {
4618 let mask = RawImage::allocate_mask(LayoutSize::zero());
4619 assert_eq!(mask.data_format, RawImageFormat::R8);
4620 assert_eq!(mask.width, 0);
4621 assert_eq!(mask.height, 0);
4622 assert_eq!(mask.pixels.get_u8_vec_ref().map(|v| v.len()), Some(0));
4623
4624 let mask = RawImage::allocate_mask(LayoutSize::new(4, 4));
4625 assert_eq!(mask.pixels.get_u8_vec_ref().map(|v| v.len()), Some(16));
4626 assert!(mask
4627 .pixels
4628 .get_u8_vec_ref()
4629 .expect("u8")
4630 .as_ref()
4631 .iter()
4632 .all(|b| *b == 0));
4633
4634 let mask = RawImage::allocate_mask(LayoutSize::new(-4, 4));
4639 assert_eq!(
4640 mask.pixels.get_u8_vec_ref().map(|v| v.len()),
4641 Some(0),
4642 "a negative extent must never allocate"
4643 );
4644 assert!(mask.width > 1_000_000, "negative width wraps via `as usize`");
4645 }
4646
4647 #[test]
4648 fn raw_image_mask_round_trips_as_r8() {
4649 let mask = RawImage::allocate_mask(LayoutSize::new(2, 2));
4652 let (data, descriptor) = mask.into_loaded_image_source().expect("consistent mask");
4653 assert_eq!(descriptor.format, RawImageFormat::R8);
4654 assert_eq!((descriptor.width, descriptor.height), (2, 2));
4655 assert!(!descriptor.flags.is_opaque, "R8 is never opaque");
4656 match data {
4657 ImageData::Raw(bytes) => assert_eq!(bytes.len(), 4),
4658 ImageData::External(_) => panic!("expected raw bytes"),
4659 }
4660 }
4661
4662 #[test]
4663 fn raw_image_rgba8_encode_decode_round_trip() {
4664 let raw = RawImage {
4667 pixels: RawImageData::U8(vec![10u8, 20, 30, 255].into()),
4668 width: 1,
4669 height: 1,
4670 premultiplied_alpha: true,
4671 data_format: RawImageFormat::RGBA8,
4672 tag: Vec::new().into(),
4673 };
4674 let img = ImageRef::new_rawimage(raw).expect("1x1 RGBA8 with 4 bytes is valid");
4675
4676 assert!(img.is_raw_image());
4677 assert!(!img.is_null_image());
4678 assert!(!img.is_gl_texture());
4679 assert!(!img.is_callback());
4680 assert_eq!(img.get_size(), LogicalSize::new(1.0, 1.0));
4681 assert_eq!(img.get_bytes(), Some(&[30u8, 20, 10, 255][..]));
4682 assert!(!img.get_bytes_ptr().is_null());
4683
4684 let decoded = img.get_rawimage().expect("raw image round-trips");
4685 assert_eq!(decoded.width, 1);
4686 assert_eq!(decoded.height, 1);
4687 assert_eq!(decoded.data_format, RawImageFormat::BGRA8);
4688 assert!(decoded.premultiplied_alpha);
4689 assert_eq!(
4690 decoded.pixels.get_u8_vec_ref().map(|v| v.as_ref().to_vec()),
4691 Some(vec![30, 20, 10, 255])
4692 );
4693 }
4694
4695 #[test]
4696 fn image_ref_new_rawimage_rejects_dimension_mismatch() {
4697 let too_small = RawImage {
4699 pixels: RawImageData::U8(vec![0u8; 4].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_small).is_none());
4707
4708 let too_big = RawImage {
4710 pixels: RawImageData::U8(vec![0u8; 64].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(too_big).is_none());
4718
4719 let wrong_type = RawImage {
4721 pixels: RawImageData::U16(vec![0u16; 16].into()),
4722 width: 2,
4723 height: 2,
4724 premultiplied_alpha: true,
4725 data_format: RawImageFormat::RGBA8,
4726 tag: Vec::new().into(),
4727 };
4728 assert!(ImageRef::new_rawimage(wrong_type).is_none());
4729 }
4730
4731 #[test]
4736 fn image_ref_null_image_predicates_and_accessors() {
4737 let img = ImageRef::null_image(0, 0, RawImageFormat::BGRA8, Vec::new());
4738 assert!(img.is_null_image());
4739 assert!(!img.is_raw_image());
4740 assert!(!img.is_gl_texture());
4741 assert!(!img.is_callback());
4742 assert_eq!(img.get_size(), LogicalSize::new(0.0, 0.0));
4743 assert!(img.get_bytes().is_none());
4744 assert!(img.get_rawimage().is_none());
4745 assert!(img.get_bytes_ptr().is_null());
4746 assert!(img.get_image_callback().is_none());
4747 assert!(matches!(img.get_data(), DecodedImage::NullImage { .. }));
4748 }
4749
4750 #[test]
4751 fn image_ref_null_image_at_usize_max_reports_a_finite_size() {
4752 let img = ImageRef::null_image(usize::MAX, usize::MAX, RawImageFormat::R8, Vec::new());
4754 let size = img.get_size();
4755 assert!(size.width.is_finite() && size.height.is_finite());
4756 assert!(size.width > 0.0 && size.height > 0.0);
4757 assert!(img.is_null_image());
4758
4759 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, vec![9u8; 10_000]);
4761 match img.get_data() {
4762 DecodedImage::NullImage { tag, .. } => assert_eq!(tag.len(), 10_000),
4763 _ => panic!("expected NullImage"),
4764 }
4765 }
4766
4767 #[test]
4768 fn image_ref_hash_identity_rules() {
4769 let a = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4770 let b = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4771 assert_ne!(a.get_hash(), b.get_hash());
4773 assert_ne!(a, b);
4774
4775 let a2 = a.clone();
4777 assert_eq!(a.get_hash(), a2.get_hash());
4778 assert_eq!(a, a2);
4779
4780 let deep = a.deep_copy();
4782 assert_ne!(a.get_hash(), deep.get_hash());
4783 assert!(deep.is_null_image());
4784 assert_eq!(deep.get_size(), a.get_size());
4785 }
4786
4787 #[test]
4788 fn image_ref_callback_accessors() {
4789 let mut img = ImageRef::callback(0usize, RefAny::new(123u32));
4792 assert!(img.is_callback());
4793 assert!(!img.is_null_image());
4794 assert!(!img.is_raw_image());
4795 assert_eq!(img.get_size(), LogicalSize::new(0.0, 0.0));
4797 assert!(img.get_bytes().is_none());
4798 assert!(img.get_bytes_ptr().is_null());
4799 assert!(img.get_rawimage().is_none());
4800
4801 assert!(img.get_image_callback().is_some());
4803 assert!(img.get_image_callback_mut().is_some());
4804
4805 let clone = img.clone();
4808 assert!(img.get_image_callback().is_none());
4809 assert!(img.get_image_callback_mut().is_none());
4810 drop(clone);
4811 assert!(img.get_image_callback().is_some());
4812 }
4813
4814 #[test]
4815 fn image_ref_deep_copy_of_a_callback_keeps_it_a_callback() {
4816 let img = ImageRef::callback(0usize, RefAny::new(1u8));
4817 let deep = img.deep_copy();
4818 assert!(deep.is_callback());
4819 assert_ne!(img.get_hash(), deep.get_hash());
4820 }
4821
4822 #[test]
4823 fn image_ref_into_inner_only_when_sole_owner() {
4824 let img = ImageRef::null_image(2, 2, RawImageFormat::RGBA8, vec![1, 2, 3]);
4825 let clone = img.clone();
4826 assert!(clone.into_inner().is_none(), "shared -> must refuse");
4827
4828 let inner = img.into_inner().expect("sole owner -> takes ownership");
4829 match inner {
4830 DecodedImage::NullImage {
4831 width,
4832 height,
4833 format,
4834 tag,
4835 } => {
4836 assert_eq!((width, height), (2, 2));
4837 assert_eq!(format, RawImageFormat::RGBA8);
4838 assert_eq!(tag, vec![1, 2, 3]);
4839 }
4840 _ => panic!("expected NullImage"),
4841 }
4842 }
4843
4844 #[test]
4849 fn image_cache_add_get_delete_round_trip() {
4850 let mut cache = ImageCache::new();
4851 let key = AzString::from_const_str("my_image");
4852 let img = ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new());
4853 let hash = img.get_hash();
4854
4855 assert!(cache.get_css_image_id(&key).is_none());
4856 cache.add_css_image_id(key.clone(), img);
4857 assert_eq!(cache.get_css_image_id(&key).map(ImageRef::get_hash), Some(hash));
4858
4859 let img2 = ImageRef::null_image(2, 2, RawImageFormat::R8, Vec::new());
4861 let hash2 = img2.get_hash();
4862 cache.add_css_image_id(key.clone(), img2);
4863 assert_eq!(cache.image_id_map.len(), 1);
4864 assert_eq!(cache.get_css_image_id(&key).map(ImageRef::get_hash), Some(hash2));
4865
4866 cache.delete_css_image_id(&key);
4867 assert!(cache.get_css_image_id(&key).is_none());
4868 assert!(cache.image_id_map.is_empty());
4869 cache.delete_css_image_id(&key);
4871 cache.delete_css_image_id(&AzString::from_const_str("never-existed"));
4872 }
4873
4874 #[test]
4875 fn image_cache_handles_empty_and_unicode_keys() {
4876 let mut cache = ImageCache::new();
4877 let empty = AzString::from_const_str("");
4878 let unicode = AzString::from(String::from("\u{1F600}\u{0301}"));
4879 let long = AzString::from("k".repeat(100_000));
4880
4881 cache.add_css_image_id(
4882 empty.clone(),
4883 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4884 );
4885 cache.add_css_image_id(
4886 unicode.clone(),
4887 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4888 );
4889 cache.add_css_image_id(
4890 long.clone(),
4891 ImageRef::null_image(1, 1, RawImageFormat::R8, Vec::new()),
4892 );
4893
4894 assert_eq!(cache.image_id_map.len(), 3);
4895 assert!(cache.get_css_image_id(&empty).is_some());
4896 assert!(cache.get_css_image_id(&unicode).is_some());
4897 assert!(cache.get_css_image_id(&long).is_some());
4898 assert!(cache
4900 .get_css_image_id(&AzString::from_const_str("\u{1F600}"))
4901 .is_none());
4902 }
4903
4904 #[test]
4909 fn renderer_resources_lookups_on_an_empty_registry_are_none() {
4910 let rr = RendererResources::default();
4911 let ns = IdNamespace(1);
4912 assert!(rr
4913 .get_renderable_font_data(&FontInstanceKey::unique(ns))
4914 .is_none());
4915 let families = StyleFontFamiliesHash::new(&[]);
4916 assert!(rr
4917 .get_font_instance_key(&families, Au(0), DpiScaleFactor::new(1.0))
4918 .is_none());
4919 assert!(rr
4920 .get_font_instance_key(&families, Au(MAX_AU), DpiScaleFactor::new(f32::NAN))
4921 .is_none());
4922 assert!(rr.get_image(&ImageRefHash { inner: 0 }).is_none());
4923 assert!(rr.get_font_key(&StyleFontFamilyHash::new(&StyleFontFamily::System(
4924 AzString::from_const_str("Arial")
4925 ))).is_none());
4926 }
4927
4928 #[test]
4929 fn renderer_resources_gc_helper_is_a_noop_on_empty_maps() {
4930 let mut rr = RendererResources::default();
4932 rr.remove_font_families_with_zero_references();
4933 assert!(rr.font_id_map.is_empty());
4934 assert!(rr.font_families_map.is_empty());
4935
4936 let family = StyleFontFamily::System(AzString::from_const_str("Arial"));
4939 let family_hash = StyleFontFamilyHash::new(&family);
4940 let families_hash = StyleFontFamiliesHash::new(core::slice::from_ref(&family));
4941 rr.font_id_map.insert(family_hash, FontKey::unique(IdNamespace(1)));
4942 rr.font_families_map.insert(families_hash, family_hash);
4943 rr.remove_font_families_with_zero_references();
4944 assert!(rr.font_id_map.is_empty(), "dangling font key must be pruned");
4945 assert!(rr.font_families_map.is_empty());
4946 }
4947
4948 #[test]
4949 fn get_font_instance_key_for_text_is_none_on_empty_resources_for_all_sane_sizes() {
4950 let rr = RendererResources::default();
4953 let cache = CssPropertyCache::default();
4954 let node = NodeData::default();
4955 let node_id = NodeId::new(0);
4956 let state = StyledNodeState::default();
4957
4958 for size in [0.0_f32, -0.0, 1.0, -12.0, f32::NAN, 1.0e6, -1.0e6] {
4959 for dpi in [1.0_f32, 0.0, -1.0, f32::NAN, f32::INFINITY] {
4960 assert!(
4961 rr.get_font_instance_key_for_text(size, &cache, &node, &node_id, &state, dpi)
4962 .is_none(),
4963 "size={size} dpi={dpi} must miss cleanly"
4964 );
4965 }
4966 }
4967 }
4968
4969 #[test]
4970 fn bug_get_font_instance_key_for_text_overflow_panics_on_infinite_font_size() {
4971 let rr = RendererResources::default();
4976 let cache = CssPropertyCache::default();
4977 let node = NodeData::default();
4978 let node_id = NodeId::new(0);
4979 let state = StyledNodeState::default();
4980 assert!(rr
4981 .get_font_instance_key_for_text(
4982 f32::INFINITY,
4983 &cache,
4984 &node,
4985 &node_id,
4986 &state,
4987 1.0
4988 )
4989 .is_none());
4990 }
4991
4992 #[test]
4997 fn font_ref_get_hash_is_stable_per_font_and_distinct_across_fonts() {
4998 let a = dummy_font_ref();
4999 let b = dummy_font_ref();
5000 assert_eq!(font_ref_get_hash(&a), font_ref_get_hash(&a));
5001 assert_eq!(font_ref_get_hash(&a), font_ref_get_hash(&a.clone()));
5002 assert_ne!(
5003 font_ref_get_hash(&a),
5004 font_ref_get_hash(&b),
5005 "two distinct FontRefs must not share a hash"
5006 );
5007 }
5008
5009 #[test]
5010 fn build_add_font_resource_updates_on_empty_input_is_empty() {
5011 let mut rr = RendererResources::default();
5012 let fonts = OrderedMap::new();
5013 let updates = build_add_font_resource_updates(
5014 &mut rr,
5015 DpiScaleFactor::new(1.0),
5016 &FcFontCache::default(),
5017 IdNamespace(1),
5018 &fonts,
5019 load_font_none,
5020 parse_font_none,
5021 );
5022 assert!(updates.is_empty());
5023 assert!(rr.font_id_map.is_empty());
5024 }
5025
5026 #[test]
5027 fn build_add_font_resource_updates_skips_unloadable_fonts() {
5028 let mut rr = RendererResources::default();
5031 let mut fonts = OrderedMap::new();
5032 let mut sizes = FastBTreeSet::new();
5033 sizes.insert(Au::from_px(16.0));
5034 fonts.insert(
5035 ImmediateFontId::Unresolved(StyleFontFamilyVec::from_vec(vec![
5036 StyleFontFamily::System(AzString::from_const_str("DoesNotExist")),
5037 ])),
5038 sizes,
5039 );
5040
5041 let updates = build_add_font_resource_updates(
5042 &mut rr,
5043 DpiScaleFactor::new(1.0),
5044 &FcFontCache::default(),
5045 IdNamespace(1),
5046 &fonts,
5047 load_font_none,
5048 parse_font_none,
5049 );
5050 assert!(updates.is_empty(), "an unloadable font must add no resources");
5051 assert!(rr.font_id_map.is_empty());
5052 assert!(rr.font_families_map.is_empty());
5053 }
5054
5055 #[test]
5056 fn build_add_font_resource_updates_registers_a_font_and_deduplicates_sizes() {
5057 let mut rr = RendererResources::default();
5060 let font = dummy_font_ref();
5061 let family = StyleFontFamily::Ref(font.clone());
5062 let dpi = DpiScaleFactor::new(1.0);
5063
5064 let mut sizes = FastBTreeSet::new();
5065 sizes.insert(Au::from_px(16.0));
5066 sizes.insert(Au::from_px(24.0));
5067 sizes.insert(Au::from_px(16.0)); assert_eq!(sizes.len(), 2);
5069
5070 let mut fonts = OrderedMap::new();
5071 fonts.insert(
5072 ImmediateFontId::Unresolved(StyleFontFamilyVec::from_vec(vec![family.clone()])),
5073 sizes,
5074 );
5075
5076 let updates = build_add_font_resource_updates(
5077 &mut rr,
5078 dpi,
5079 &FcFontCache::default(),
5080 IdNamespace(1),
5081 &fonts,
5082 load_font_none,
5083 parse_font_none,
5084 );
5085 assert_eq!(updates.len(), 3);
5087 assert_eq!(
5088 updates
5089 .iter()
5090 .filter(|(_, m)| matches!(m, AddFontMsg::Font(..)))
5091 .count(),
5092 1
5093 );
5094 assert_eq!(
5095 updates
5096 .iter()
5097 .filter(|(_, m)| matches!(m, AddFontMsg::Instance(..)))
5098 .count(),
5099 2
5100 );
5101 assert_eq!(rr.font_id_map.len(), 1);
5102 assert_eq!(rr.font_families_map.len(), 1);
5103
5104 let mut all_updates = Vec::new();
5106 add_resources(&mut rr, &mut all_updates, updates, Vec::new());
5107 assert_eq!(all_updates.len(), 3);
5108
5109 let families_hash = StyleFontFamiliesHash::new(core::slice::from_ref(&family));
5110 assert!(rr
5111 .get_font_instance_key(&families_hash, Au::from_px(16.0), dpi)
5112 .is_some());
5113 assert!(rr
5114 .get_font_instance_key(&families_hash, Au::from_px(24.0), dpi)
5115 .is_some());
5116 assert!(rr
5118 .get_font_instance_key(&families_hash, Au::from_px(99.0), dpi)
5119 .is_none());
5120 assert!(rr
5121 .get_font_instance_key(&families_hash, Au::from_px(16.0), DpiScaleFactor::new(2.0))
5122 .is_none());
5123
5124 let key = rr
5126 .get_font_instance_key(&families_hash, Au::from_px(16.0), dpi)
5127 .expect("registered");
5128 let (font_ref, au, got_dpi) = rr
5129 .get_renderable_font_data(&key)
5130 .expect("registered instance must be renderable");
5131 assert_eq!(font_ref.get_hash(), font.get_hash());
5132 assert_eq!(au, Au::from_px(16.0));
5133 assert_eq!(got_dpi, dpi);
5134
5135 let again = build_add_font_resource_updates(
5137 &mut rr,
5138 dpi,
5139 &FcFontCache::default(),
5140 IdNamespace(1),
5141 &fonts,
5142 load_font_none,
5143 parse_font_none,
5144 );
5145 assert!(again.is_empty(), "already-registered fonts must not be re-added");
5146 }
5147
5148 #[test]
5149 fn add_font_msg_into_resource_update_preserves_keys() {
5150 let font = dummy_font_ref();
5151 let key = FontKey::unique(IdNamespace(3));
5152 let family_hash = StyleFontFamilyHash::new(&StyleFontFamily::Ref(font.clone()));
5153 let msg = AddFontMsg::Font(key, family_hash, font.clone());
5154 match msg.into_resource_update() {
5155 ResourceUpdate::AddFont(add) => {
5156 assert_eq!(add.key, key);
5157 assert_eq!(add.font.get_hash(), font.get_hash());
5158 }
5159 other => panic!("expected AddFont, got {other:?}"),
5160 }
5161 }
5162
5163 #[test]
5164 fn delete_font_msg_into_resource_update_preserves_keys() {
5165 let fk = FontKey::unique(IdNamespace(1));
5166 match DeleteFontMsg::Font(fk).into_resource_update() {
5167 ResourceUpdate::DeleteFont(k) => assert_eq!(k, fk),
5168 other => panic!("expected DeleteFont, got {other:?}"),
5169 }
5170 let fik = FontInstanceKey::unique(IdNamespace(1));
5171 let size = (Au::from_px(16.0), DpiScaleFactor::new(1.0));
5172 match DeleteFontMsg::Instance(fik, size).into_resource_update() {
5173 ResourceUpdate::DeleteFontInstance(k) => assert_eq!(k, fik),
5174 other => panic!("expected DeleteFontInstance, got {other:?}"),
5175 }
5176 }
5177
5178 #[test]
5179 fn add_image_msg_into_resource_update_preserves_the_key_and_descriptor() {
5180 let key = ImageKey::unique(IdNamespace(2));
5181 let descriptor = ImageDescriptor {
5182 format: RawImageFormat::BGRA8,
5183 width: 3,
5184 height: 5,
5185 stride: None.into(),
5186 offset: 0,
5187 flags: ImageDescriptorFlags {
5188 is_opaque: false,
5189 allow_mipmaps: true,
5190 },
5191 };
5192 let msg = AddImageMsg(AddImage {
5193 key,
5194 descriptor,
5195 data: ImageData::Raw(SharedRawImageData::new(vec![0u8; 60].into())),
5196 tiling: None,
5197 });
5198 match msg.into_resource_update() {
5199 ResourceUpdate::AddImage(add) => {
5200 assert_eq!(add.key, key);
5201 assert_eq!(add.descriptor, descriptor);
5202 assert!(add.tiling.is_none());
5203 }
5204 other => panic!("expected AddImage, got {other:?}"),
5205 }
5206 }
5207
5208 #[test]
5209 fn build_add_image_resource_updates_skips_null_and_callback_images() {
5210 let rr = RendererResources::default();
5212 let mut images = FastBTreeSet::new();
5213 images.insert(ImageRef::null_image(4, 4, RawImageFormat::RGBA8, Vec::new()));
5214 images.insert(ImageRef::callback(0usize, RefAny::new(0u8)));
5215
5216 let updates = build_add_image_resource_updates(
5217 &rr,
5218 IdNamespace(1),
5219 Epoch::new(),
5220 &test_document_id(),
5221 &images,
5222 store_gl_texture_noop,
5223 );
5224 assert!(updates.is_empty());
5225
5226 let empty = FastBTreeSet::new();
5228 assert!(build_add_image_resource_updates(
5229 &rr,
5230 IdNamespace(1),
5231 Epoch::new(),
5232 &test_document_id(),
5233 &empty,
5234 store_gl_texture_noop,
5235 )
5236 .is_empty());
5237 }
5238
5239 #[test]
5240 fn build_add_image_resource_updates_then_add_resources_round_trip() {
5241 let mut rr = RendererResources::default();
5242 let img = ImageRef::new_rawimage(rgba8_image(2, 2)).expect("valid 2x2");
5243 let hash = img.get_hash();
5244 let ns = IdNamespace(11);
5245
5246 let mut images = FastBTreeSet::new();
5247 images.insert(img.clone());
5248
5249 let updates = build_add_image_resource_updates(
5250 &rr,
5251 ns,
5252 Epoch::new(),
5253 &test_document_id(),
5254 &images,
5255 store_gl_texture_noop,
5256 );
5257 assert_eq!(updates.len(), 1);
5258 assert_eq!(updates[0].0, hash);
5259 assert_eq!(updates[0].1 .0.key, image_ref_hash_to_image_key(hash, ns));
5261 assert_eq!(updates[0].1 .0.descriptor.width, 2);
5262 assert_eq!(updates[0].1 .0.descriptor.height, 2);
5263
5264 let key = updates[0].1 .0.key;
5265 let mut all_updates = Vec::new();
5266 add_resources(&mut rr, &mut all_updates, Vec::new(), updates);
5267 assert_eq!(all_updates.len(), 1);
5268 assert!(matches!(all_updates[0], ResourceUpdate::AddImage(_)));
5269
5270 assert_eq!(rr.get_image(&hash).map(|r| r.key), Some(key));
5272 assert_eq!(rr.image_key_map.get(&key), Some(&hash));
5273
5274 let again = build_add_image_resource_updates(
5276 &rr,
5277 ns,
5278 Epoch::new(),
5279 &test_document_id(),
5280 &images,
5281 store_gl_texture_noop,
5282 );
5283 assert!(again.is_empty());
5284
5285 let new_descriptor = ImageDescriptor {
5287 format: RawImageFormat::BGRA8,
5288 width: 8,
5289 height: 8,
5290 stride: None.into(),
5291 offset: 0,
5292 flags: ImageDescriptorFlags {
5293 is_opaque: true,
5294 allow_mipmaps: true,
5295 },
5296 };
5297 rr.update_image(&hash, new_descriptor);
5298 assert_eq!(rr.get_image(&hash).map(|r| r.descriptor.width), Some(8));
5299 assert_eq!(rr.get_image(&hash).map(|r| r.key), Some(key));
5300 rr.update_image(&ImageRefHash { inner: u64::MAX }, new_descriptor);
5302 }
5303
5304 #[test]
5305 fn add_resources_with_empty_input_changes_nothing() {
5306 let mut rr = RendererResources::default();
5307 let mut updates = Vec::new();
5308 add_resources(&mut rr, &mut updates, Vec::new(), Vec::new());
5309 assert!(updates.is_empty());
5310 assert!(rr.currently_registered_images.is_empty());
5311 assert!(rr.currently_registered_fonts.is_empty());
5312 assert!(rr.image_key_map.is_empty());
5313 }
5314
5315 #[test]
5320 fn paint_dot_composites_at_the_center_and_leaves_far_pixels_alone() {
5321 let mut img = rgba8_image(4, 4);
5322 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5323 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5324
5325 let idx = (4 + 1) * 4;
5327 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255], "center pixel must be opaque red");
5328 assert_eq!(&px[0..4], &[0, 0, 0, 0], "pixels beyond the radius stay untouched");
5330 }
5331
5332 #[test]
5333 fn paint_dot_honours_bgra_channel_order() {
5334 let mut img = rgba8_image(4, 4);
5335 img.data_format = RawImageFormat::BGRA8;
5336 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5337 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5338 let idx = (4 + 1) * 4;
5339 assert_eq!(&px[idx..idx + 4], &[0, 0, 255, 255]);
5341 }
5342
5343 #[test]
5344 fn paint_dot_rejects_degenerate_radii_and_sizes() {
5345 let untouched = |img: &RawImage| {
5346 img.pixels
5347 .get_u8_vec_ref()
5348 .expect("u8")
5349 .as_ref()
5350 .iter()
5351 .all(|b| *b == 0)
5352 };
5353
5354 for r in [0.0_f32, -1.0, -0.0, f32::NAN, f32::NEG_INFINITY] {
5356 let mut img = rgba8_image(4, 4);
5357 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), r));
5358 assert!(untouched(&img), "radius {r} must not paint");
5359 }
5360
5361 let mut img = rgba8_image(0, 0);
5363 img.paint_dot(0.0, 0.0, Brush::new(opaque_red(), 4.0));
5364 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(0));
5365
5366 for format in [
5368 RawImageFormat::R8,
5369 RawImageFormat::RGB8,
5370 RawImageFormat::RGBA16,
5371 RawImageFormat::RGBAF32,
5372 ] {
5373 let mut img = rgba8_image(4, 4);
5374 img.data_format = format;
5375 img.paint_dot(2.0, 2.0, Brush::new(opaque_red(), 2.0));
5376 assert!(untouched(&img), "format {format:?} must not be painted");
5377 }
5378 }
5379
5380 #[test]
5381 fn paint_dot_with_nan_and_infinite_coordinates_is_a_safe_noop() {
5382 for (cx, cy) in [
5385 (f32::NAN, 2.0_f32),
5386 (2.0, f32::NAN),
5387 (f32::NAN, f32::NAN),
5388 (f32::INFINITY, 2.0),
5389 (f32::NEG_INFINITY, 2.0),
5390 (2.0, f32::INFINITY),
5391 (1.0e30, 1.0e30),
5392 (-1.0e30, -1.0e30),
5393 ] {
5394 let mut img = rgba8_image(4, 4);
5395 img.paint_dot(cx, cy, Brush::new(opaque_red(), 2.0));
5396 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5397 assert!(
5398 px.iter().all(|b| *b == 0),
5399 "({cx}, {cy}) must not paint anything"
5400 );
5401 }
5402 }
5403
5404 #[test]
5405 fn paint_dot_alpha_saturates_and_never_overflows() {
5406 let mut img = rgba8_image(4, 4);
5408 let brush = Brush::new(opaque_red(), 2.0);
5409 for _ in 0..50 {
5410 img.paint_dot(2.0, 2.0, brush);
5411 }
5412 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5413 let idx = (4 + 1) * 4;
5414 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255]);
5415
5416 let mut img = rgba8_image(4, 4);
5421 let mut nan_brush = Brush::new(opaque_red(), 2.0);
5422 nan_brush.hardness = f32::NAN;
5423 img.paint_dot(2.0, 2.0, nan_brush);
5424 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(64));
5427 }
5428
5429 #[test]
5430 fn paint_dot_zero_flow_and_transparent_color_do_not_paint() {
5431 let mut img = rgba8_image(4, 4);
5432 let mut brush = Brush::new(opaque_red(), 2.0);
5433 brush.flow = 0.0;
5434 img.paint_dot(2.0, 2.0, brush);
5435 assert!(img
5436 .pixels
5437 .get_u8_vec_ref()
5438 .expect("u8")
5439 .as_ref()
5440 .iter()
5441 .all(|b| *b == 0));
5442
5443 let mut img = rgba8_image(4, 4);
5444 let transparent = ColorU {
5445 r: 255,
5446 g: 0,
5447 b: 0,
5448 a: 0,
5449 };
5450 img.paint_dot(2.0, 2.0, Brush::new(transparent, 2.0));
5451 assert!(img
5452 .pixels
5453 .get_u8_vec_ref()
5454 .expect("u8")
5455 .as_ref()
5456 .iter()
5457 .all(|b| *b == 0));
5458
5459 let mut img = rgba8_image(4, 4);
5461 let mut brush = Brush::new(opaque_red(), 2.0);
5462 brush.flow = -5.0;
5463 img.paint_dot(2.0, 2.0, brush);
5464 assert!(img
5465 .pixels
5466 .get_u8_vec_ref()
5467 .expect("u8")
5468 .as_ref()
5469 .iter()
5470 .all(|b| *b == 0));
5471 }
5472
5473 #[test]
5474 fn paint_stroke_paints_both_endpoints() {
5475 let mut img = rgba8_image(8, 8);
5476 img.paint_stroke(1.5, 1.5, 6.5, 6.5, Brush::new(opaque_red(), 1.5));
5477 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5478 let alpha_at = |x: usize, y: usize| px[(y * 8 + x) * 4 + 3];
5479 assert!(alpha_at(1, 1) > 0, "start of the stroke must be painted");
5480 assert!(alpha_at(6, 6) > 0, "end of the stroke must be painted");
5481 assert_eq!(alpha_at(7, 0), 0, "off-line pixels stay untouched");
5482 }
5483
5484 #[test]
5485 fn paint_stroke_zero_length_stamps_a_single_dab() {
5486 let mut img = rgba8_image(4, 4);
5488 img.paint_stroke(2.0, 2.0, 2.0, 2.0, Brush::new(opaque_red(), 2.0));
5489 let px = img.pixels.get_u8_vec_ref().expect("u8").as_ref().to_vec();
5490 let idx = (4 + 1) * 4;
5491 assert_eq!(&px[idx..idx + 4], &[255, 0, 0, 255]);
5492 }
5493
5494 #[test]
5495 fn paint_stroke_degenerate_brush_params_do_not_divide_by_zero_or_hang() {
5496 for spacing in [0.0_f32, -1.0, f32::NAN] {
5499 let mut img = rgba8_image(8, 8);
5500 let mut brush = Brush::new(opaque_red(), 2.0);
5501 brush.spacing = spacing;
5502 img.paint_stroke(0.0, 0.0, 7.0, 7.0, brush);
5503 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(8 * 8 * 4));
5504 }
5505
5506 let mut img = rgba8_image(4, 4);
5508 img.paint_stroke(f32::NAN, 0.0, 1.0, 1.0, Brush::new(opaque_red(), 1.0));
5509 assert_eq!(img.pixels.get_u8_vec_ref().map(|v| v.len()), Some(64));
5510
5511 let mut img = rgba8_image(4, 4);
5513 img.paint_stroke(0.0, 0.0, 3.0, 3.0, Brush::new(opaque_red(), 0.0));
5514 assert!(img
5515 .pixels
5516 .get_u8_vec_ref()
5517 .expect("u8")
5518 .as_ref()
5519 .iter()
5520 .all(|b| *b == 0));
5521 }
5522
5523 #[test]
5524 fn bug_paint_stroke_with_infinite_endpoint_loops_2_billion_times() {
5525 let mut img = rgba8_image(4, 4);
5529 img.paint_stroke(0.0, 0.0, f32::INFINITY, 0.0, Brush::new(opaque_red(), 2.0));
5530 }
5531
5532 #[test]
5533 fn bug_paint_dot_indexes_out_of_bounds_when_dims_exceed_the_buffer() {
5534 let mut img = RawImage {
5539 pixels: RawImageData::U8(vec![0u8; 4].into()),
5540 width: 100,
5541 height: 100,
5542 premultiplied_alpha: true,
5543 data_format: RawImageFormat::RGBA8,
5544 tag: Vec::new().into(),
5545 };
5546 img.paint_dot(50.0, 50.0, Brush::new(opaque_red(), 4.0));
5547 }
5548
5549 #[test]
5550 fn bug_into_loaded_image_source_overflows_on_huge_dimensions() {
5551 let img = RawImage {
5555 pixels: RawImageData::U8(vec![0u8; 4].into()),
5556 width: usize::MAX,
5557 height: 2,
5558 premultiplied_alpha: true,
5559 data_format: RawImageFormat::RGBA8,
5560 tag: Vec::new().into(),
5561 };
5562 assert!(img.into_loaded_image_source().is_none());
5563 }
5564}