1#[cfg(not(feature = "std"))]
13use alloc::string::ToString;
14use alloc::{boxed::Box, collections::btree_map::BTreeMap, string::String, vec::Vec};
15use core::{
16 fmt,
17 hash::{Hash, Hasher},
18 sync::atomic::{AtomicU64, AtomicUsize, Ordering as AtomicOrdering},
19};
20
21use azul_css::{
22 codegen::format::GetHash,
23 props::basic::{
24 pixel::DEFAULT_FONT_SIZE, ColorU, FloatValue, FontRef, LayoutRect, LayoutSize,
25 StyleFontFamily, StyleFontFamilyVec, StyleFontSize,
26 },
27 props::style::scrollbar::OptionScrollPhysics,
28 system::SystemStyle,
29 AzString, F32Vec, LayoutDebugMessage, OptionI32, StringVec, U16Vec, U32Vec, U8Vec,
30};
31use rust_fontconfig::FcFontCache;
32
33pub use crate::callbacks::{
35 CoreImageCallback, CoreRenderImageCallback, CoreRenderImageCallbackType,
36};
37use crate::{
38 callbacks::{LayoutCallback, VirtualViewCallback},
39 dom::{DomId, NodeData, NodeType},
40 geom::{LogicalPosition, LogicalRect, LogicalSize},
41 gl::{OptionGlContextPtr, Texture},
42 hit_test::DocumentId,
43 id::NodeId,
44 prop_cache::CssPropertyCache,
45 refany::RefAny,
46 styled_dom::{
47 NodeHierarchyItemId, StyleFontFamiliesHash, StyleFontFamilyHash, StyledDom, StyledNodeState,
48 },
49 ui_solver::GlyphInstance,
50 window::{AzStringPair, OptionChar, StringPairVec},
51 xml::{
52 ComponentDef, ComponentDefVec, ComponentId, ComponentLibrary, ComponentLibraryVec,
53 ComponentSource, RegisterComponentFn, RegisterComponentLibraryFn,
54 },
55 FastBTreeSet, OrderedMap,
56};
57
58#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
64#[repr(C)]
65pub enum UpdateImageType {
66 Background,
68 Content,
70}
71
72#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
73#[repr(C)]
74pub struct DpiScaleFactor {
75 pub inner: FloatValue,
76}
77
78impl DpiScaleFactor {
79 #[must_use]
80 pub fn new(f: f32) -> Self {
81 Self {
82 inner: FloatValue::new(f),
83 }
84 }
85}
86
87#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
89#[repr(C)]
90#[derive(Default)]
91pub enum AppTerminationBehavior {
92 ReturnToMain,
96 RunForever,
99 #[default]
102 EndProcess,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
111#[repr(C)]
112pub struct EmailAddress {
113 pub address: AzString,
115}
116
117impl_option!(
118 EmailAddress,
119 OptionEmailAddress,
120 copy = false,
121 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
122);
123
124impl EmailAddress {
125 #[must_use]
126 pub const fn new(address: AzString) -> Self {
127 Self { address }
128 }
129}
130
131#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
138#[repr(C)]
139pub enum UpdateMode {
140 SelfUpdate,
143 #[default]
146 NotifyOnly,
147 Disabled,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
155#[repr(C)]
156pub struct UpdateSettings {
157 pub mode: UpdateMode,
159 pub manifest_url: azul_css::OptionString,
162 pub current_version: AzString,
165 pub app_name: AzString,
168 pub build_date: AzString,
172 pub build_tag: AzString,
175 pub channel: AzString,
181 pub root_public_key: AzString,
187}
188
189impl Default for UpdateSettings {
190 fn default() -> Self {
191 Self {
192 mode: UpdateMode::default(),
193 manifest_url: azul_css::OptionString::None,
194 current_version: AzString::from_const_str("0.0.0"),
195 app_name: AzString::from_const_str("azul-app"),
196 build_date: AzString::from_const_str(""),
197 build_tag: AzString::from_const_str(""),
198 channel: AzString::from_const_str(""),
199 root_public_key: AzString::from_const_str(""),
200 }
201 }
202}
203
204#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
207#[repr(C)]
208pub struct NamedFont {
209 pub name: AzString,
211 pub bytes: U8Vec,
213}
214
215impl_option!(
216 NamedFont,
217 OptionNamedFont,
218 copy = false,
219 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
220);
221
222impl NamedFont {
223 #[must_use]
224 pub const fn new(name: AzString, bytes: U8Vec) -> Self {
225 Self { name, bytes }
226 }
227}
228
229impl_vec!(
230 NamedFont,
231 NamedFontVec,
232 NamedFontVecDestructor,
233 NamedFontVecDestructorType,
234 NamedFontVecSlice,
235 OptionNamedFont
236);
237impl_vec_mut!(NamedFont, NamedFontVec);
238impl_vec_debug!(NamedFont, NamedFontVec);
239impl_vec_partialeq!(NamedFont, NamedFontVec);
240impl_vec_eq!(NamedFont, NamedFontVec);
241impl_vec_partialord!(NamedFont, NamedFontVec);
242impl_vec_ord!(NamedFont, NamedFontVec);
243impl_vec_hash!(NamedFont, NamedFontVec);
244impl_vec_clone!(NamedFont, NamedFontVec, NamedFontVecDestructor);
245
246#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
255#[repr(C)]
256pub struct LoadedFont {
257 pub font_hash: u64,
261 pub family_name: AzString,
264 pub num_glyphs: u32,
266 pub has_bytes: bool,
271}
272
273impl_option!(
274 LoadedFont,
275 OptionLoadedFont,
276 copy = false,
277 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
278);
279
280impl LoadedFont {
281 #[must_use]
282 pub const fn new(
283 font_hash: u64,
284 family_name: AzString,
285 num_glyphs: u32,
286 has_bytes: bool,
287 ) -> Self {
288 Self {
289 font_hash,
290 family_name,
291 num_glyphs,
292 has_bytes,
293 }
294 }
295}
296
297impl_vec!(
298 LoadedFont,
299 LoadedFontVec,
300 LoadedFontVecDestructor,
301 LoadedFontVecDestructorType,
302 LoadedFontVecSlice,
303 OptionLoadedFont
304);
305impl_vec_mut!(LoadedFont, LoadedFontVec);
306impl_vec_debug!(LoadedFont, LoadedFontVec);
307impl_vec_partialeq!(LoadedFont, LoadedFontVec);
308impl_vec_eq!(LoadedFont, LoadedFontVec);
309impl_vec_partialord!(LoadedFont, LoadedFontVec);
310impl_vec_ord!(LoadedFont, LoadedFontVec);
311impl_vec_hash!(LoadedFont, LoadedFontVec);
312impl_vec_clone!(LoadedFont, LoadedFontVec, LoadedFontVecDestructor);
313#[allow(variant_size_differences)]
314#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
317#[repr(C, u8)]
318#[derive(Default)]
319pub enum FontLoadingConfig {
320 #[default]
322 LoadAllSystemFonts,
323 LoadOnlyFamilies(StringVec),
326 BundledFontsOnly,
328}
329
330#[derive(Debug, Clone, Default)]
359#[repr(C)]
360pub struct CssMockEnvironment {
361 pub theme: azul_css::dynamic_selector::OptionThemeCondition,
363 pub language: azul_css::OptionString,
365 pub os_version: azul_css::dynamic_selector::OptionOsVersion,
367 pub os: azul_css::dynamic_selector::OptionOsCondition,
369 pub desktop_env: azul_css::dynamic_selector::OptionLinuxDesktopEnv,
371 pub viewport_width: azul_css::OptionF32,
374 pub viewport_height: azul_css::OptionF32,
375 pub prefers_reduced_motion: azul_css::OptionBool,
377 pub prefers_high_contrast: azul_css::OptionBool,
379}
380
381impl CssMockEnvironment {
382 #[must_use]
384 pub fn linux() -> Self {
385 Self {
386 os: azul_css::dynamic_selector::OptionOsCondition::Some(
387 azul_css::dynamic_selector::OsCondition::Linux,
388 ),
389 ..Default::default()
390 }
391 }
392
393 #[must_use]
395 pub fn windows() -> Self {
396 Self {
397 os: azul_css::dynamic_selector::OptionOsCondition::Some(
398 azul_css::dynamic_selector::OsCondition::Windows,
399 ),
400 ..Default::default()
401 }
402 }
403
404 #[must_use]
406 pub fn macos() -> Self {
407 Self {
408 os: azul_css::dynamic_selector::OptionOsCondition::Some(
409 azul_css::dynamic_selector::OsCondition::MacOS,
410 ),
411 ..Default::default()
412 }
413 }
414
415 #[must_use]
417 pub fn dark_theme() -> Self {
418 Self {
419 theme: azul_css::dynamic_selector::OptionThemeCondition::Some(
420 azul_css::dynamic_selector::ThemeCondition::Dark,
421 ),
422 ..Default::default()
423 }
424 }
425
426 #[must_use]
428 pub fn light_theme() -> Self {
429 Self {
430 theme: azul_css::dynamic_selector::OptionThemeCondition::Some(
431 azul_css::dynamic_selector::ThemeCondition::Light,
432 ),
433 ..Default::default()
434 }
435 }
436
437 pub fn apply_to(&self, ctx: &mut azul_css::dynamic_selector::DynamicSelectorContext) {
439 if let azul_css::dynamic_selector::OptionOsCondition::Some(os) = self.os {
440 ctx.os = os;
441 }
442 if let azul_css::dynamic_selector::OptionOsVersion::Some(os_version) = self.os_version {
443 ctx.os_version = os_version;
444 }
445 if let azul_css::dynamic_selector::OptionLinuxDesktopEnv::Some(de) = self.desktop_env {
446 ctx.desktop_env = azul_css::dynamic_selector::OptionLinuxDesktopEnv::Some(de);
447 }
448 if let azul_css::dynamic_selector::OptionThemeCondition::Some(ref theme) = self.theme {
449 ctx.theme = theme.clone();
450 }
451 if let azul_css::OptionString::Some(ref lang) = self.language {
452 ctx.language = lang.clone();
453 }
454 if let azul_css::OptionBool::Some(reduced) = self.prefers_reduced_motion {
455 ctx.prefers_reduced_motion = if reduced {
456 azul_css::dynamic_selector::BoolCondition::True
457 } else {
458 azul_css::dynamic_selector::BoolCondition::False
459 };
460 }
461 if let azul_css::OptionBool::Some(high_contrast) = self.prefers_high_contrast {
462 ctx.prefers_high_contrast = if high_contrast {
463 azul_css::dynamic_selector::BoolCondition::True
464 } else {
465 azul_css::dynamic_selector::BoolCondition::False
466 };
467 }
468 if let azul_css::OptionF32::Some(w) = self.viewport_width {
469 ctx.viewport_width = w;
470 }
471 if let azul_css::OptionF32::Some(h) = self.viewport_height {
472 ctx.viewport_height = h;
473 }
474 }
475}
476
477impl_option!(
478 CssMockEnvironment,
479 OptionCssMockEnvironment,
480 copy = false,
481 [Debug, Clone]
482);
483
484#[repr(C)]
501pub struct Route {
502 pub pattern: AzString,
504 pub layout_callback: LayoutCallback,
506}
507
508impl Clone for Route {
509 fn clone(&self) -> Self {
510 Self {
511 pattern: self.pattern.clone(),
512 layout_callback: self.layout_callback.clone(),
513 }
514 }
515}
516impl fmt::Debug for Route {
517 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
518 f.debug_struct("Route")
519 .field("pattern", &self.pattern)
520 .field("layout_callback", &self.layout_callback)
521 .finish()
522 }
523}
524impl PartialEq for Route {
525 fn eq(&self, o: &Self) -> bool {
526 self.pattern == o.pattern && self.layout_callback == o.layout_callback
527 }
528}
529impl Eq for Route {}
530impl PartialOrd for Route {
531 fn partial_cmp(&self, o: &Self) -> Option<core::cmp::Ordering> {
532 Some(self.cmp(o))
533 }
534}
535impl Ord for Route {
536 fn cmp(&self, o: &Self) -> core::cmp::Ordering {
537 self.pattern
538 .cmp(&o.pattern)
539 .then_with(|| self.layout_callback.cmp(&o.layout_callback))
540 }
541}
542impl Hash for Route {
543 fn hash<H: Hasher>(&self, state: &mut H) {
544 self.pattern.hash(state);
545 self.layout_callback.hash(state);
546 }
547}
548
549impl_option!(
550 Route,
551 OptionRoute,
552 copy = false,
553 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
554);
555impl_vec!(
556 Route,
557 RouteVec,
558 RouteVecDestructor,
559 RouteVecDestructorType,
560 RouteVecSlice,
561 OptionRoute
562);
563impl_vec_mut!(Route, RouteVec);
564impl_vec_debug!(Route, RouteVec);
565impl_vec_clone!(Route, RouteVec, RouteVecDestructor);
566impl_vec_partialeq!(Route, RouteVec);
567impl_vec_eq!(Route, RouteVec);
568impl_vec_partialord!(Route, RouteVec);
569impl_vec_ord!(Route, RouteVec);
570impl_vec_hash!(Route, RouteVec);
571
572#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
577#[repr(C)]
578pub struct RouteMatch {
579 pub pattern: AzString,
581 pub params: StringPairVec,
583}
584
585impl RouteMatch {
586 #[must_use]
588 pub fn get_param(&self, key: &str) -> Option<&AzString> {
589 self.params.get_key(key)
590 }
591}
592
593impl_option!(
594 RouteMatch,
595 OptionRouteMatch,
596 copy = false,
597 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
598);
599
600#[allow(clippy::similar_names)] #[must_use]
610pub fn match_route(pattern: &str, path: &str) -> Option<RouteMatch> {
611 let pat_segs: Vec<&str> = pattern.split('/').filter(|s| !s.is_empty()).collect();
612 let path_segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
613
614 if pat_segs.len() != path_segs.len() {
615 return None;
616 }
617
618 let mut params = Vec::new();
619 for (pat, val) in pat_segs.iter().zip(path_segs.iter()) {
620 if let Some(param_name) = pat.strip_prefix(':') {
621 params.push(AzStringPair {
622 key: AzString::from(param_name.to_string()),
623 value: AzString::from((*val).to_string()),
624 });
625 } else if pat != val {
626 return None;
627 }
628 }
629
630 Some(RouteMatch {
631 pattern: AzString::from(pattern.to_string()),
632 params: StringPairVec::from_vec(params),
633 })
634}
635
636#[derive(Debug)]
647#[repr(C)]
648pub struct ZombieAnimInfo {
649 pub styled_dom: *const crate::styled_dom::StyledDom,
653 pub node_id: u64,
655 pub rect: LogicalRect,
658 pub viewport: LogicalRect,
660 pub dpi_factor: f32,
661 pub t: f32,
666 pub timing: azul_css::props::basic::animation::AnimationTiming,
669 pub velocity_x: f32,
675 pub velocity_y: f32,
676}
677
678#[derive(Debug, Clone, Copy, PartialEq)]
681#[repr(C)]
682pub struct ZombieFrame {
683 pub translate_x: f32,
685 pub translate_y: f32,
686 pub opacity: f32,
688 pub width: azul_css::OptionF32,
692 pub clip_to_frozen_rect: bool,
695}
696
697impl Default for ZombieFrame {
698 fn default() -> Self {
699 Self {
700 translate_x: 0.0,
701 translate_y: 0.0,
702 opacity: 1.0,
703 width: azul_css::OptionF32::None,
704 clip_to_frozen_rect: true,
705 }
706 }
707}
708
709#[repr(C)]
722#[derive(Clone, Copy)]
723pub struct ZombieAnimCallback {
724 pub cb: usize,
725}
726
727impl fmt::Debug for ZombieAnimCallback {
728 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
729 write!(f, "ZombieAnimCallback @ 0x{:x}", self.cb)
730 }
731}
732impl Hash for ZombieAnimCallback {
733 fn hash<H: Hasher>(&self, state: &mut H) {
734 state.write_usize(self.cb);
735 }
736}
737impl PartialEq for ZombieAnimCallback {
738 fn eq(&self, other: &Self) -> bool {
739 self.cb == other.cb
740 }
741}
742impl Eq for ZombieAnimCallback {}
743impl PartialOrd for ZombieAnimCallback {
744 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
745 Some(self.cmp(other))
746 }
747}
748impl Ord for ZombieAnimCallback {
749 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
750 self.cb.cmp(&other.cb)
751 }
752}
753
754#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
763#[repr(C)]
764pub struct AnimationFunction {
765 pub name: AzString,
766 pub callback: ZombieAnimCallback,
767 pub data: RefAny,
769}
770
771#[derive(Debug, Clone)]
780#[repr(C)]
781pub struct SystemAnimations {
782 pub caret_tween: crate::callbacks::CaretTweenCallback,
790 pub selection_tween: crate::callbacks::SelectionTweenCallback,
795 pub caret_tween_data: RefAny,
797 pub selection_tween_data: RefAny,
799
800 pub scroll_physics: OptionScrollPhysics,
803 pub caret_tween_duration_ms: u32,
807 pub selection_tween_duration_ms: u32,
809 pub focus_ring_duration_ms: u32,
816 pub caret_scroll_glide: bool,
820}
821
822impl SystemAnimations {
823 #[must_use]
827 pub fn disabled() -> Self {
828 Self {
829 caret_tween_duration_ms: 0,
830 selection_tween_duration_ms: 0,
831 caret_scroll_glide: false,
832 focus_ring_duration_ms: 0,
833 ..Self::default()
834 }
835 }
836}
837
838impl Default for SystemAnimations {
839 fn default() -> Self {
840 Self {
841 scroll_physics: OptionScrollPhysics::None,
842 caret_tween_duration_ms: 60,
845 caret_tween: crate::callbacks::CaretTweenCallback::create(
846 crate::callbacks::default_caret_tween,
847 ),
848 caret_tween_data: RefAny::new(()),
849 selection_tween_duration_ms: 60,
850 selection_tween: crate::callbacks::SelectionTweenCallback::create(
851 crate::callbacks::default_selection_tween,
852 ),
853 selection_tween_data: RefAny::new(()),
854 caret_scroll_glide: true,
855 focus_ring_duration_ms: 60,
863 }
864 }
865}
866
867#[derive(Debug, Clone)]
869#[repr(C)]
870pub struct AppConfig {
871 pub log_level: AppLogLevel,
875 pub natural_scroll: NaturalScroll,
888 pub enable_visual_panic_hook: bool,
891 pub enable_logging_on_panic: bool,
894 pub synthesize_pinch_from_ctrl_wheel: bool,
912 pub expose_system_media_controls: bool,
936 pub termination_behavior: AppTerminationBehavior,
939 pub icon_provider: crate::icon::IconProviderHandle,
943 pub bundled_fonts: NamedFontVec,
946 pub font_loading: FontLoadingConfig,
949 pub mock_css_environment: OptionCssMockEnvironment,
959 pub system_style: SystemStyle,
965 pub component_libraries: ComponentLibraryVec,
975 pub routes: RouteVec,
982 pub system_animations: SystemAnimations,
985 pub custom_e2e_op: crate::events::CustomE2eOpCallback,
992 pub updates: UpdateSettings,
997 pub changelog_md: azul_css::OptionString,
1001 pub report_problem: OptionEmailAddress,
1005}
1006
1007impl AppConfig {
1008 #[must_use]
1009 pub fn create() -> Self {
1010 let log_level = AppLogLevel::Error;
1011 let icon_provider = crate::icon::IconProviderHandle::new();
1012 let bundled_fonts = NamedFontVec::from_const_slice(&[]);
1013 let font_loading = FontLoadingConfig::default();
1014 let system_style = SystemStyle::detect();
1015 let mut s = Self {
1016 log_level,
1017 enable_visual_panic_hook: true,
1018 enable_logging_on_panic: true,
1019 termination_behavior: AppTerminationBehavior::default(),
1020 icon_provider,
1021 bundled_fonts,
1022 font_loading,
1023 mock_css_environment: OptionCssMockEnvironment::None,
1024 system_style,
1025 component_libraries: ComponentLibraryVec::from_const_slice(&[]),
1026 routes: RouteVec::from_const_slice(&[]),
1027 system_animations: SystemAnimations::default(),
1028 synthesize_pinch_from_ctrl_wheel: true,
1031 natural_scroll: NaturalScroll::Disabled,
1033 expose_system_media_controls: false,
1035 custom_e2e_op: crate::events::CustomE2eOpCallback::default(),
1036 updates: UpdateSettings::default(),
1037 changelog_md: azul_css::OptionString::None,
1038 report_problem: OptionEmailAddress::None,
1039 };
1040 let register_builtin: crate::xml::RegisterComponentLibraryFnType =
1045 crate::xml::register_builtin_components;
1046 s.add_component_library(AzString::from_const_str("builtin"), register_builtin);
1047 s
1048 }
1049
1050 #[must_use]
1067 pub fn with_mock_environment(mut self, env: CssMockEnvironment) -> Self {
1068 self.mock_css_environment = OptionCssMockEnvironment::Some(env);
1069 self
1070 }
1071
1072 pub fn add_component<R: Into<RegisterComponentFn>>(
1084 &mut self,
1085 library: AzString,
1086 register_fn: R,
1087 ) {
1088 let register_fn = register_fn.into();
1089 let component = (register_fn.cb)();
1090 let empty_libs = ComponentLibraryVec::from_const_slice(&[]);
1091 let mut libs =
1092 core::mem::replace(&mut self.component_libraries, empty_libs).into_library_owned_vec();
1093
1094 if let Some(existing_lib) = libs
1095 .iter_mut()
1096 .find(|l| l.name.as_str() == library.as_str())
1097 {
1098 let empty_comps = ComponentDefVec::from_const_slice(&[]);
1099 let mut comps = core::mem::replace(&mut existing_lib.components, empty_comps)
1100 .into_library_owned_vec();
1101 if let Some(ec) = comps
1102 .iter_mut()
1103 .find(|c| c.id.name.as_str() == component.id.name.as_str())
1104 {
1105 *ec = component;
1106 } else {
1107 comps.push(component);
1108 }
1109 existing_lib.components = ComponentDefVec::from_vec(comps);
1110 } else {
1111 libs.push(ComponentLibrary {
1112 name: library,
1113 version: AzString::from_const_str("1.0.0"),
1114 description: AzString::from_const_str(""),
1115 components: ComponentDefVec::from_vec(alloc::vec![component]),
1116 exportable: true,
1117 modifiable: true,
1118 data_models: crate::xml::ComponentDataModelVec::from_const_slice(&[]),
1119 enum_models: crate::xml::ComponentEnumModelVec::from_const_slice(&[]),
1120 });
1121 }
1122
1123 self.component_libraries = ComponentLibraryVec::from_vec(libs);
1124 }
1125
1126 pub fn add_component_library<R: Into<RegisterComponentLibraryFn>>(
1138 &mut self,
1139 name: AzString,
1140 register_fn: R,
1141 ) {
1142 let register_fn = register_fn.into();
1143 let mut library = (register_fn.cb)();
1144 library.name = name;
1145
1146 let empty_libs = ComponentLibraryVec::from_const_slice(&[]);
1147 let mut libs =
1148 core::mem::replace(&mut self.component_libraries, empty_libs).into_library_owned_vec();
1149 if let Some(existing) = libs
1150 .iter_mut()
1151 .find(|l| l.name.as_str() == library.name.as_str())
1152 {
1153 *existing = library;
1154 } else {
1155 libs.push(library);
1156 }
1157
1158 self.component_libraries = ComponentLibraryVec::from_vec(libs);
1159 }
1160
1161 pub fn add_route<P: Into<AzString>, L: Into<LayoutCallback>>(
1172 &mut self,
1173 pattern: P,
1174 layout_fn: L,
1175 ) {
1176 let route = Route {
1177 pattern: pattern.into(),
1178 layout_callback: layout_fn.into(),
1179 };
1180 let empty = RouteVec::from_const_slice(&[]);
1181 let mut routes = core::mem::replace(&mut self.routes, empty).into_library_owned_vec();
1182 if let Some(existing) = routes
1184 .iter_mut()
1185 .find(|r| r.pattern.as_str() == route.pattern.as_str())
1186 {
1187 *existing = route;
1188 } else {
1189 routes.push(route);
1190 }
1191 self.routes = RouteVec::from_vec(routes);
1192 }
1193
1194 #[must_use]
1198 pub fn match_route_for_path(&self, path: &str) -> Option<(&Route, RouteMatch)> {
1199 for route in self.routes.as_ref() {
1200 if let Some(m) = match_route(route.pattern.as_str(), path) {
1201 return Some((route, m));
1202 }
1203 }
1204 None
1205 }
1206}
1207
1208impl Default for AppConfig {
1209 fn default() -> Self {
1210 Self::create()
1211 }
1212}
1213
1214#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1217#[repr(C)]
1218pub enum NaturalScroll {
1219 #[default]
1222 Disabled,
1223 Enabled,
1225 System,
1228}
1229
1230#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1231#[repr(C)]
1232pub enum AppLogLevel {
1233 Off,
1234 Error,
1235 Warn,
1236 Info,
1237 Debug,
1238 Trace,
1239}
1240
1241#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1243#[repr(C)]
1244pub struct ImageDescriptor {
1245 pub format: RawImageFormat,
1247 pub width: usize,
1249 pub height: usize,
1250 pub stride: OptionI32,
1255 pub offset: i32,
1261 pub flags: ImageDescriptorFlags,
1263}
1264
1265#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1267#[repr(C)]
1268pub struct ImageDescriptorFlags {
1269 pub is_opaque: bool,
1272 pub allow_mipmaps: bool,
1278}
1279
1280#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1281pub struct IdNamespace(pub u32);
1282
1283impl ::core::fmt::Display for IdNamespace {
1284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1285 write!(f, "IdNamespace({})", self.0)
1286 }
1287}
1288
1289impl ::core::fmt::Debug for IdNamespace {
1290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1291 write!(f, "{self}")
1292 }
1293}
1294
1295#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1296#[repr(C)]
1297pub enum RawImageFormat {
1298 R8,
1299 RG8,
1300 RGB8,
1301 RGBA8,
1302 R16,
1303 RG16,
1304 RGB16,
1305 RGBA16,
1306 BGR8,
1307 BGRA8,
1308 RGBF32,
1309 RGBAF32,
1310}
1311
1312static IMAGE_KEY: AtomicU64 = AtomicU64::new(1);
1314static FONT_KEY: AtomicU64 = AtomicU64::new(0);
1315static FONT_INSTANCE_KEY: AtomicU64 = AtomicU64::new(0);
1316
1317#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1318pub struct ImageKey {
1319 pub namespace: IdNamespace,
1320 pub key: u64,
1321}
1322
1323impl ImageKey {
1324 pub const DUMMY: Self = Self {
1325 namespace: IdNamespace(0),
1326 key: 0,
1327 };
1328
1329 pub fn unique(render_api_namespace: IdNamespace) -> Self {
1330 Self {
1331 namespace: render_api_namespace,
1332 key: IMAGE_KEY.fetch_add(1, AtomicOrdering::SeqCst),
1333 }
1334 }
1335}
1336
1337#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1338pub struct FontKey {
1339 pub namespace: IdNamespace,
1340 pub key: u64,
1341}
1342
1343impl FontKey {
1344 pub fn unique(render_api_namespace: IdNamespace) -> Self {
1345 Self {
1346 namespace: render_api_namespace,
1347 key: FONT_KEY.fetch_add(1, AtomicOrdering::SeqCst),
1348 }
1349 }
1350}
1351
1352#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1353pub struct FontInstanceKey {
1354 pub namespace: IdNamespace,
1355 pub key: u64,
1356}
1357
1358impl FontInstanceKey {
1359 pub fn unique(render_api_namespace: IdNamespace) -> Self {
1360 Self {
1361 namespace: render_api_namespace,
1362 key: FONT_INSTANCE_KEY.fetch_add(1, AtomicOrdering::SeqCst),
1363 }
1364 }
1365}
1366
1367#[derive(Debug)]
1370pub enum DecodedImage {
1371 NullImage {
1374 width: usize,
1375 height: usize,
1376 format: RawImageFormat,
1377 tag: Vec<u8>,
1379 },
1380 Gl(Texture),
1382 Raw((ImageDescriptor, ImageData)),
1384 Callback(CoreImageCallback),
1386 }
1391
1392#[derive(Debug)]
1393#[repr(C)]
1394pub struct ImageRef {
1395 pub data: *const DecodedImage,
1397 pub copies: *const AtomicUsize,
1399 pub id: u64,
1407 pub run_destructor: bool,
1408}
1409
1410static IMAGE_REF_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
1413
1414#[must_use]
1415fn next_image_ref_id() -> u64 {
1416 IMAGE_REF_ID_COUNTER.fetch_add(1, AtomicOrdering::SeqCst)
1417}
1418
1419impl ImageRef {
1420 #[must_use]
1421 pub const fn get_hash(&self) -> ImageRefHash {
1422 image_ref_get_hash(self)
1423 }
1424}
1425
1426#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Ord, Eq)]
1427#[repr(C)]
1428pub struct ImageRefHash {
1429 pub inner: u64,
1430}
1431
1432impl_option!(
1433 ImageRef,
1434 OptionImageRef,
1435 copy = false,
1436 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1437);
1438
1439impl ImageRef {
1440 #[must_use]
1442 pub fn into_inner(self) -> Option<DecodedImage> {
1443 unsafe {
1448 if self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) == Some(1) {
1449 let data = Box::from_raw(self.data.cast_mut());
1450 drop(Box::from_raw(self.copies.cast_mut()));
1451 core::mem::forget(self); Some(*data)
1453 } else {
1454 None
1455 }
1456 }
1457 }
1458
1459 #[must_use]
1460 pub const fn get_data(&self) -> &DecodedImage {
1461 unsafe { &*self.data }
1465 }
1466
1467 #[must_use]
1468 pub fn get_image_callback(&self) -> Option<&CoreImageCallback> {
1469 if unsafe { self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) != Some(1) } {
1471 return None; }
1473
1474 match unsafe { &*self.data } {
1476 DecodedImage::Callback(gl_texture_callback) => Some(gl_texture_callback),
1477 _ => None,
1478 }
1479 }
1480
1481 pub fn get_image_callback_mut(&mut self) -> Option<&mut CoreImageCallback> {
1482 if unsafe { self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) != Some(1) } {
1484 return None; }
1486
1487 match unsafe { &mut *self.data.cast_mut() } {
1490 DecodedImage::Callback(gl_texture_callback) => Some(gl_texture_callback),
1491 _ => None,
1492 }
1493 }
1494
1495 #[must_use]
1497 pub fn deep_copy(&self) -> Self {
1498 let new_data = match self.get_data() {
1499 DecodedImage::NullImage {
1500 width,
1501 height,
1502 format,
1503 tag,
1504 } => DecodedImage::NullImage {
1505 width: *width,
1506 height: *height,
1507 format: *format,
1508 tag: tag.clone(),
1509 },
1510 DecodedImage::Gl(tex) => DecodedImage::NullImage {
1514 width: tex.size.width as usize,
1515 height: tex.size.height as usize,
1516 format: tex.format,
1517 tag: Vec::new(),
1518 },
1519 DecodedImage::Raw((descriptor, data)) => DecodedImage::Raw((*descriptor, data.clone())),
1522 DecodedImage::Callback(cb) => DecodedImage::Callback(cb.clone()),
1523 };
1524
1525 Self::new(new_data)
1526 }
1527
1528 #[must_use]
1529 pub const fn is_null_image(&self) -> bool {
1530 matches!(self.get_data(), DecodedImage::NullImage { .. })
1531 }
1532
1533 #[must_use]
1534 pub const fn is_gl_texture(&self) -> bool {
1535 matches!(self.get_data(), DecodedImage::Gl(_))
1536 }
1537
1538 #[must_use]
1539 pub const fn is_raw_image(&self) -> bool {
1540 matches!(self.get_data(), DecodedImage::Raw((_, _)))
1541 }
1542
1543 #[must_use]
1544 pub const fn is_callback(&self) -> bool {
1545 matches!(self.get_data(), DecodedImage::Callback(_))
1546 }
1547
1548 #[must_use]
1550 pub fn get_rawimage(&self) -> Option<RawImage> {
1551 match self.get_data() {
1552 DecodedImage::Raw((image_descriptor, image_data)) => Some(RawImage {
1553 pixels: match image_data {
1554 ImageData::Raw(shared_data) => {
1555 let data_clone = shared_data.clone();
1558 data_clone.into_inner().map_or_else(
1559 || RawImageData::U8(shared_data.as_ref().to_vec().into()),
1560 RawImageData::U8,
1561 )
1562 }
1563 ImageData::External(_) => return None,
1564 },
1565 width: image_descriptor.width,
1566 height: image_descriptor.height,
1567 premultiplied_alpha: true,
1568 data_format: image_descriptor.format,
1569 tag: Vec::new().into(),
1570 }),
1571 _ => None,
1572 }
1573 }
1574
1575 #[must_use]
1578 pub fn get_bytes(&self) -> Option<&[u8]> {
1579 match self.get_data() {
1580 DecodedImage::Raw((_, image_data)) => match image_data {
1581 ImageData::Raw(shared_data) => Some(shared_data.as_ref()),
1582 ImageData::External(_) => None,
1583 },
1584 _ => None,
1585 }
1586 }
1587
1588 #[must_use]
1591 pub fn get_bytes_ptr(&self) -> *const u8 {
1592 match self.get_data() {
1593 DecodedImage::Raw((_, image_data)) => match image_data {
1594 ImageData::Raw(shared_data) => shared_data.as_ptr(),
1595 ImageData::External(_) => core::ptr::null(),
1596 },
1597 _ => core::ptr::null(),
1598 }
1599 }
1600
1601 #[allow(clippy::cast_precision_loss)] #[must_use]
1604 pub const fn get_size(&self) -> LogicalSize {
1605 match self.get_data() {
1606 DecodedImage::NullImage { width, height, .. } => {
1607 LogicalSize::new(*width as f32, *height as f32)
1608 }
1609 DecodedImage::Gl(tex) => {
1610 LogicalSize::new(tex.size.width as f32, tex.size.height as f32)
1611 }
1612 DecodedImage::Raw((image_descriptor, _)) => LogicalSize::new(
1613 image_descriptor.width as f32,
1614 image_descriptor.height as f32,
1615 ),
1616 DecodedImage::Callback(_) => LogicalSize::new(0.0, 0.0),
1617 }
1618 }
1619
1620 #[must_use]
1621 pub fn null_image(width: usize, height: usize, format: RawImageFormat, tag: Vec<u8>) -> Self {
1622 Self::new(DecodedImage::NullImage {
1623 width,
1624 height,
1625 format,
1626 tag,
1627 })
1628 }
1629
1630 pub fn callback<C: Into<CoreRenderImageCallback>>(callback: C, data: RefAny) -> Self {
1631 Self::new(DecodedImage::Callback(CoreImageCallback {
1632 callback: callback.into(),
1633 refany: data,
1634 }))
1635 }
1636
1637 #[must_use]
1638 pub fn new_rawimage(image_data: RawImage) -> Option<Self> {
1639 let (image_data, image_descriptor) = image_data.into_loaded_image_source()?;
1640 Some(Self::new(DecodedImage::Raw((image_descriptor, image_data))))
1641 }
1642
1643 #[must_use]
1644 pub fn new_gltexture(texture: Texture) -> Self {
1645 Self::new(DecodedImage::Gl(texture))
1646 }
1647
1648 fn new(data: DecodedImage) -> Self {
1649 Self {
1650 data: Box::into_raw(Box::new(data)),
1651 copies: Box::into_raw(Box::new(AtomicUsize::new(1))),
1652 id: next_image_ref_id(),
1653 run_destructor: true,
1654 }
1655 }
1656
1657 }
1659
1660unsafe impl Send for ImageRef {}
1664unsafe impl Sync for ImageRef {}
1665
1666impl PartialEq for ImageRef {
1671 fn eq(&self, rhs: &Self) -> bool {
1672 self.id == rhs.id
1673 }
1674}
1675
1676impl PartialOrd for ImageRef {
1677 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
1678 Some(self.id.cmp(&other.id))
1679 }
1680}
1681
1682impl Ord for ImageRef {
1683 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1684 self.id.cmp(&other.id)
1685 }
1686}
1687
1688impl Eq for ImageRef {}
1689
1690impl Hash for ImageRef {
1691 fn hash<H>(&self, state: &mut H)
1692 where
1693 H: Hasher,
1694 {
1695 self.id.hash(state);
1696 }
1697}
1698
1699impl Clone for ImageRef {
1700 fn clone(&self) -> Self {
1701 unsafe {
1704 self.copies
1705 .as_ref()
1706 .map(|m| m.fetch_add(1, AtomicOrdering::SeqCst));
1707 }
1708 Self {
1709 data: self.data, copies: self.copies, id: self.id, run_destructor: true,
1713 }
1714 }
1715}
1716
1717impl Drop for ImageRef {
1718 fn drop(&mut self) {
1719 self.run_destructor = false;
1720 unsafe {
1724 let copies = (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst);
1725 if copies == 1 {
1726 drop(Box::from_raw(self.data.cast_mut()));
1727 drop(Box::from_raw(self.copies.cast_mut()));
1728 }
1729 }
1730 }
1731}
1732
1733#[must_use]
1734pub const fn image_ref_get_hash(ir: &ImageRef) -> ImageRefHash {
1735 ImageRefHash { inner: ir.id }
1741}
1742
1743#[must_use]
1750pub const fn image_ref_hash_to_image_key(hash: ImageRefHash, namespace: IdNamespace) -> ImageKey {
1751 ImageKey {
1752 namespace,
1753 key: hash.inner,
1754 }
1755}
1756
1757#[must_use]
1758pub fn font_ref_get_hash(fr: &FontRef) -> u64 {
1759 fr.get_hash()
1760}
1761
1762#[derive(Debug, Default)]
1768pub struct ImageCache {
1769 pub image_id_map: OrderedMap<AzString, ImageRef>,
1775}
1776
1777impl ImageCache {
1778 #[must_use]
1779 pub fn new() -> Self {
1780 Self::default()
1781 }
1782
1783 pub fn add_css_image_id(&mut self, css_id: AzString, image: ImageRef) {
1786 self.image_id_map.insert(css_id, image);
1787 }
1788
1789 #[must_use]
1790 pub fn get_css_image_id(&self, css_id: &AzString) -> Option<&ImageRef> {
1791 self.image_id_map.get(css_id)
1792 }
1793
1794 pub fn delete_css_image_id(&mut self, css_id: &AzString) {
1795 self.image_id_map.remove(css_id);
1796 }
1797}
1798
1799#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1800pub struct ResolvedImage {
1801 pub key: ImageKey,
1802 pub descriptor: ImageDescriptor,
1803}
1804
1805pub trait RendererResourcesTrait: fmt::Debug {
1807 fn get_font_family(
1809 &self,
1810 style_font_families_hash: &StyleFontFamiliesHash,
1811 ) -> Option<&StyleFontFamilyHash>;
1812
1813 fn get_font_key(&self, style_font_family_hash: &StyleFontFamilyHash) -> Option<&FontKey>;
1815
1816 fn get_registered_font(
1818 &self,
1819 font_key: &FontKey,
1820 ) -> Option<&(FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)>;
1821
1822 fn get_image(&self, hash: &ImageRefHash) -> Option<&ResolvedImage>;
1824
1825 fn update_image(&mut self, image_ref_hash: &ImageRefHash, descriptor: ImageDescriptor);
1827}
1828
1829impl RendererResourcesTrait for RendererResources {
1831 fn get_font_family(
1832 &self,
1833 style_font_families_hash: &StyleFontFamiliesHash,
1834 ) -> Option<&StyleFontFamilyHash> {
1835 self.font_families_map.get(style_font_families_hash)
1836 }
1837
1838 fn get_font_key(&self, style_font_family_hash: &StyleFontFamilyHash) -> Option<&FontKey> {
1839 self.font_id_map.get(style_font_family_hash)
1840 }
1841
1842 fn get_registered_font(
1843 &self,
1844 font_key: &FontKey,
1845 ) -> Option<&(FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)> {
1846 self.currently_registered_fonts.get(font_key)
1847 }
1848
1849 fn get_image(&self, hash: &ImageRefHash) -> Option<&ResolvedImage> {
1850 self.currently_registered_images.get(hash)
1851 }
1852
1853 fn update_image(&mut self, image_ref_hash: &ImageRefHash, descriptor: ImageDescriptor) {
1854 if let Some(s) = self.currently_registered_images.get_mut(image_ref_hash) {
1855 s.descriptor = descriptor;
1856 }
1857 }
1858}
1859
1860#[derive(Default)]
1867pub struct RendererResources {
1868 pub currently_registered_images: OrderedMap<ImageRefHash, ResolvedImage>,
1870 pub image_key_map: OrderedMap<ImageKey, ImageRefHash>,
1872 pub image_last_seen_epoch: OrderedMap<ImageRefHash, u32>,
1879 pub currently_registered_fonts:
1881 OrderedMap<FontKey, (FontRef, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>)>,
1882 pub last_frame_registered_fonts:
1889 OrderedMap<FontKey, OrderedMap<(Au, DpiScaleFactor), FontInstanceKey>>,
1890 pub font_families_map: OrderedMap<StyleFontFamiliesHash, StyleFontFamilyHash>,
1895 pub font_id_map: OrderedMap<StyleFontFamilyHash, FontKey>,
1897 pub font_hash_map: OrderedMap<u64, FontKey>,
1900}
1901
1902impl fmt::Debug for RendererResources {
1903 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1904 write!(
1905 f,
1906 "RendererResources {{
1907 currently_registered_images: {:#?},
1908 currently_registered_fonts: {:#?},
1909 font_families_map: {:#?},
1910 font_id_map: {:#?},
1911 }}",
1912 self.currently_registered_images.keys().collect::<Vec<_>>(),
1913 self.currently_registered_fonts.keys().collect::<Vec<_>>(),
1914 self.font_families_map.keys().collect::<Vec<_>>(),
1915 self.font_id_map.keys().collect::<Vec<_>>(),
1916 )
1917 }
1918}
1919
1920impl RendererResources {
1921 #[must_use]
1922 pub fn get_renderable_font_data(
1923 &self,
1924 font_instance_key: &FontInstanceKey,
1925 ) -> Option<(&FontRef, Au, DpiScaleFactor)> {
1926 self.currently_registered_fonts
1927 .iter()
1928 .find_map(|(font_key, (font_ref, instances))| {
1929 instances.iter().find_map(|((au, dpi), instance_key)| {
1930 if *instance_key == *font_instance_key {
1931 Some((font_ref, *au, *dpi))
1932 } else {
1933 None
1934 }
1935 })
1936 })
1937 }
1938
1939 #[allow(clippy::cast_possible_truncation)] pub fn get_font_instance_key_for_text(
1941 &self,
1942 font_size_px: f32,
1943 css_property_cache: &CssPropertyCache,
1944 node_data: &NodeData,
1945 node_id: &NodeId,
1946 styled_node_state: &StyledNodeState,
1947 dpi_scale: f32,
1948 ) -> Option<FontInstanceKey> {
1949 let font_size_isize = (font_size_px as isize).clamp(isize::MIN / 1000, isize::MAX / 1000);
1957 let font_size = StyleFontSize {
1958 inner: azul_css::props::basic::PixelValue::const_px(font_size_isize),
1959 };
1960
1961 let font_size_au = font_size_to_au(font_size);
1963
1964 let dpi_scale_factor = DpiScaleFactor {
1966 inner: FloatValue::new(dpi_scale),
1967 };
1968
1969 let font_family =
1971 css_property_cache.get_font_id_or_default(node_data, node_id, styled_node_state);
1972
1973 let font_families_hash = StyleFontFamiliesHash::new(font_family.as_ref());
1975
1976 self.get_font_instance_key(&font_families_hash, font_size_au, dpi_scale_factor)
1977 }
1978
1979 #[must_use]
1980 pub fn get_font_instance_key(
1981 &self,
1982 font_families_hash: &StyleFontFamiliesHash,
1983 font_size_au: Au,
1984 dpi_scale: DpiScaleFactor,
1985 ) -> Option<FontInstanceKey> {
1986 let font_family_hash = self.get_font_family(font_families_hash)?;
1987 let font_key = self.get_font_key(font_family_hash)?;
1988 let (_, instances) = self.get_registered_font(font_key)?;
1989 instances.get(&(font_size_au, dpi_scale)).copied()
1990 }
1991
1992 #[allow(dead_code)]
2023 fn remove_font_families_with_zero_references(&mut self) {
2024 let font_family_to_delete = self
2025 .font_id_map
2026 .iter()
2027 .filter_map(|(font_family, font_key)| {
2028 if self.currently_registered_fonts.contains_key(font_key) {
2029 None
2030 } else {
2031 Some(*font_family)
2032 }
2033 })
2034 .collect::<Vec<_>>();
2035
2036 for f in font_family_to_delete {
2037 self.font_id_map.remove(&f); }
2039
2040 let font_families_to_delete = self
2041 .font_families_map
2042 .iter()
2043 .filter_map(|(font_families, font_family)| {
2044 if self.font_id_map.contains_key(font_family) {
2045 None
2046 } else {
2047 Some(*font_families)
2048 }
2049 })
2050 .collect::<Vec<_>>();
2051
2052 for f in font_families_to_delete {
2053 self.font_families_map.remove(&f); }
2055 }
2056}
2057
2058#[derive(Debug, Clone)]
2069pub struct UpdateImageResult {
2070 pub key_to_update: ImageKey,
2071 pub new_descriptor: ImageDescriptor,
2072 pub new_image_data: ImageData,
2073}
2074
2075#[derive(Debug, Default)]
2076pub struct GlTextureCache {
2077 pub solved_textures:
2078 BTreeMap<DomId, BTreeMap<NodeId, (ImageKey, ImageDescriptor, ExternalImageId)>>,
2079 pub hashes: BTreeMap<(DomId, NodeId, ImageRefHash), ImageRefHash>,
2080}
2081
2082unsafe impl Send for GlTextureCache {}
2087
2088impl GlTextureCache {
2089 #[must_use]
2091 pub const fn empty() -> Self {
2092 Self {
2093 solved_textures: BTreeMap::new(),
2094 hashes: BTreeMap::new(),
2095 }
2096 }
2097
2098 pub fn update_texture(
2117 &mut self,
2118 dom_id: DomId,
2119 node_id: NodeId,
2120 document_id: DocumentId,
2121 epoch: Epoch,
2122 new_texture: Texture,
2123 insert_into_active_gl_textures_fn: &GlStoreImageFn,
2124 ) -> Option<ExternalImageId> {
2125 let new_descriptor = new_texture.get_descriptor();
2126 let di_map = self.solved_textures.get_mut(&dom_id)?;
2127 let entry = di_map.get_mut(&node_id)?;
2128
2129 entry.1 = new_descriptor;
2131
2132 let external_image_id = texture_external_image_id(dom_id, node_id);
2135 (insert_into_active_gl_textures_fn)(document_id, epoch, new_texture, external_image_id);
2136 entry.2 = external_image_id;
2137
2138 Some(external_image_id)
2139 }
2140}
2141
2142macro_rules! unique_id {
2143 ($struct_name:ident, $counter_name:ident) => {
2144 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
2145 #[repr(C)]
2146 pub struct $struct_name {
2147 pub id: usize,
2148 }
2149
2150 impl $struct_name {
2151 pub fn unique() -> Self {
2152 Self {
2153 id: $counter_name.fetch_add(1, AtomicOrdering::SeqCst),
2154 }
2155 }
2156 }
2157 };
2158}
2159
2160static PROPERTY_KEY_COUNTER: AtomicUsize = AtomicUsize::new(0);
2162unique_id!(TransformKey, PROPERTY_KEY_COUNTER);
2163unique_id!(ColorKey, PROPERTY_KEY_COUNTER);
2164unique_id!(OpacityKey, PROPERTY_KEY_COUNTER);
2165
2166static IMAGE_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
2167unique_id!(ImageId, IMAGE_ID_COUNTER);
2168static FONT_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
2169unique_id!(FontId, FONT_ID_COUNTER);
2170
2171#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2172#[repr(C)]
2173pub struct ImageMask {
2174 pub image: ImageRef,
2175 pub rect: LogicalRect,
2176 pub repeat: bool,
2177}
2178
2179impl_option!(
2180 ImageMask,
2181 OptionImageMask,
2182 copy = false,
2183 [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
2184);
2185
2186#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2187pub enum ImmediateFontId {
2188 Resolved((StyleFontFamilyHash, FontKey)),
2189 Unresolved(StyleFontFamilyVec),
2190}
2191
2192#[derive(Debug, Clone, PartialEq, PartialOrd)]
2193#[repr(C, u8)]
2194pub enum RawImageData {
2195 U8(U8Vec),
2197 U16(U16Vec),
2199 F32(F32Vec),
2201}
2202
2203impl RawImageData {
2204 #[must_use]
2205 pub const fn get_u8_vec_ref(&self) -> Option<&U8Vec> {
2206 match self {
2207 Self::U8(v) => Some(v),
2208 _ => None,
2209 }
2210 }
2211
2212 #[must_use]
2213 pub const fn get_u16_vec_ref(&self) -> Option<&U16Vec> {
2214 match self {
2215 Self::U16(v) => Some(v),
2216 _ => None,
2217 }
2218 }
2219
2220 #[must_use]
2221 pub const fn get_f32_vec_ref(&self) -> Option<&F32Vec> {
2222 match self {
2223 Self::F32(v) => Some(v),
2224 _ => None,
2225 }
2226 }
2227
2228 fn get_u8_vec(self) -> Option<U8Vec> {
2229 match self {
2230 Self::U8(v) => Some(v),
2231 _ => None,
2232 }
2233 }
2234
2235 fn get_u16_vec(self) -> Option<U16Vec> {
2236 match self {
2237 Self::U16(v) => Some(v),
2238 _ => None,
2239 }
2240 }
2241}
2242
2243#[derive(Debug, Clone, PartialEq, PartialOrd)]
2244#[repr(C)]
2245pub struct RawImage {
2246 pub pixels: RawImageData,
2247 pub width: usize,
2248 pub height: usize,
2249 pub premultiplied_alpha: bool,
2250 pub data_format: RawImageFormat,
2251 pub tag: U8Vec,
2252}
2253
2254#[repr(C)]
2260#[derive(Debug, Copy, Clone, PartialEq)]
2261pub struct Brush {
2262 pub color: ColorU,
2264 pub radius: f32,
2266 pub hardness: f32,
2269 pub flow: f32,
2272 pub spacing: f32,
2275}
2276
2277impl Brush {
2278 #[must_use]
2280 pub const fn new(color: ColorU, radius: f32) -> Self {
2281 Self {
2282 color,
2283 radius,
2284 hardness: 0.5,
2285 flow: 1.0,
2286 spacing: 0.25,
2287 }
2288 }
2289}
2290
2291#[allow(clippy::suboptimal_flops)] #[inline]
2298#[must_use]
2299pub fn brush_dab_coverage(t: f32, hardness: f32) -> f32 {
2300 let edge0 = hardness.clamp(0.0, 1.0);
2301 let denom = (1.0 - edge0).max(1.0e-4);
2302 let x = ((t - edge0) / denom).clamp(0.0, 1.0);
2303 1.0 - (x * x * (3.0 - 2.0 * x))
2304}
2305
2306impl RawImage {
2307 #[allow(clippy::suboptimal_flops)] #[allow(
2313 clippy::cast_possible_truncation,
2314 clippy::cast_precision_loss,
2315 clippy::cast_sign_loss
2316 )] #[allow(clippy::cast_possible_wrap)] pub fn paint_dot(&mut self, cx: f32, cy: f32, brush: Brush) {
2319 let r = brush.radius;
2320 #[allow(clippy::neg_cmp_op_on_partial_ord)]
2322 if !(r > 0.0) || self.width == 0 || self.height == 0 {
2323 return;
2324 }
2325 let bgr = match self.data_format {
2326 RawImageFormat::RGBA8 => false,
2327 RawImageFormat::BGRA8 => true,
2328 _ => return,
2329 };
2330 let (w, h) = (self.width as i32, self.height as i32);
2331 let buf: &mut [u8] = match self.pixels {
2332 RawImageData::U8(ref mut v) => v.as_mut(),
2333 _ => return,
2334 };
2335 let flow = brush.flow.clamp(0.0, 1.0) * (f32::from(brush.color.a) / 255.0);
2336 let (cr, cg, cb) = (
2337 f32::from(brush.color.r),
2338 f32::from(brush.color.g),
2339 f32::from(brush.color.b),
2340 );
2341 let x0 = (cx - r).floor().max(0.0) as i32;
2342 let y0 = (cy - r).floor().max(0.0) as i32;
2343 let x1 = ((cx + r).ceil() as i32).min(w);
2344 let y1 = ((cy + r).ceil() as i32).min(h);
2345 for y in y0..y1 {
2346 for x in x0..x1 {
2347 let dx = x as f32 + 0.5 - cx;
2348 let dy = y as f32 + 0.5 - cy;
2349 let dist = dx.hypot(dy);
2350 if dist > r {
2351 continue;
2352 }
2353 let a = brush_dab_coverage(dist / r, brush.hardness) * flow;
2354 if a <= 0.0 {
2355 continue;
2356 }
2357 let idx = ((y * w + x) as usize) * 4;
2358 if idx + 4 > buf.len() {
2362 continue;
2363 }
2364 let (ri, gi, bi, ai) = if bgr {
2365 (idx + 2, idx + 1, idx, idx + 3)
2366 } else {
2367 (idx, idx + 1, idx + 2, idx + 3)
2368 };
2369 let inv = 1.0 - a;
2370 buf[ri] = (cr * a + f32::from(buf[ri]) * inv)
2371 .round()
2372 .clamp(0.0, 255.0) as u8;
2373 buf[gi] = (cg * a + f32::from(buf[gi]) * inv)
2374 .round()
2375 .clamp(0.0, 255.0) as u8;
2376 buf[bi] = (cb * a + f32::from(buf[bi]) * inv)
2377 .round()
2378 .clamp(0.0, 255.0) as u8;
2379 buf[ai] = ((a + (f32::from(buf[ai]) / 255.0) * inv) * 255.0)
2380 .round()
2381 .clamp(0.0, 255.0) as u8;
2382 }
2383 }
2384 }
2385
2386 #[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) {
2392 let dx = x1 - x0;
2393 let dy = y1 - y0;
2394 let len = dx.hypot(dy);
2395 if !len.is_finite() {
2399 return;
2400 }
2401 let step = (brush.radius * brush.spacing.max(0.01)).max(0.5);
2402 let n = (len / step).floor() as i32;
2403 if n <= 0 {
2404 self.paint_dot(x1, y1, brush);
2405 return;
2406 }
2407 for i in 0..=n {
2408 let t = i as f32 / n as f32;
2409 self.paint_dot(x0 + dx * t, y0 + dy * t, brush);
2410 }
2411 }
2412}
2413
2414#[inline]
2419#[allow(clippy::cast_possible_truncation)] fn premultiply_alpha(array: &mut [u8]) {
2421 if array.len() != 4 {
2422 return;
2423 }
2424 let a = u32::from(array[3]);
2425 array[0] = (((u32::from(array[0]) * a) + 128) / 255) as u8;
2426 array[1] = (((u32::from(array[1]) * a) + 128) / 255) as u8;
2427 array[2] = (((u32::from(array[2]) * a) + 128) / 255) as u8;
2428}
2429
2430#[inline]
2431#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] fn normalize_u16(i: u16) -> u8 {
2434 ((f32::from(i) / f32::from(core::u16::MAX)) * f32::from(core::u8::MAX)) as u8
2435}
2436
2437const FOUR_BPP: usize = 4;
2438const TWO_CHANNELS: usize = 2;
2439const THREE_CHANNELS: usize = 3;
2440const FOUR_CHANNELS: usize = 4;
2441
2442impl RawImage {
2443 #[must_use]
2445 pub fn null_image() -> Self {
2446 Self {
2447 pixels: RawImageData::U8(Vec::new().into()),
2448 width: 0,
2449 height: 0,
2450 premultiplied_alpha: true,
2451 data_format: RawImageFormat::BGRA8,
2452 tag: Vec::new().into(),
2453 }
2454 }
2455
2456 #[allow(clippy::cast_sign_loss)] #[must_use]
2459 pub fn allocate_mask(size: LayoutSize) -> Self {
2460 Self {
2461 pixels: RawImageData::U8(
2462 vec![0; size.width.max(0) as usize * size.height.max(0) as usize].into(),
2463 ),
2464 width: size.width as usize,
2465 height: size.height as usize,
2466 premultiplied_alpha: true,
2467 data_format: RawImageFormat::R8,
2468 tag: Vec::new().into(),
2469 }
2470 }
2471
2472 #[must_use]
2478 pub fn into_loaded_image_source(self) -> Option<(ImageData, ImageDescriptor)> {
2479 let Self {
2480 width,
2481 height,
2482 pixels,
2483 data_format,
2484 premultiplied_alpha,
2485 tag,
2486 } = self;
2487
2488 let expected_len = width.checked_mul(height)?;
2491
2492 expected_len.checked_mul(FOUR_BPP)?;
2502
2503 let (bytes, data_format, is_opaque): (U8Vec, RawImageFormat, bool) = match data_format {
2504 RawImageFormat::R8 => {
2505 let (bytes, is_opaque) = Self::load_r8(pixels, expected_len)?;
2506 (bytes, RawImageFormat::R8, is_opaque)
2507 }
2508 RawImageFormat::RG8 => {
2509 let (bytes, is_opaque) = Self::load_rg8(pixels, expected_len, premultiplied_alpha)?;
2510 (bytes, RawImageFormat::BGRA8, is_opaque)
2511 }
2512 RawImageFormat::RGB8 => {
2513 let (bytes, is_opaque) = Self::load_rgb8(pixels, expected_len)?;
2514 (bytes, RawImageFormat::BGRA8, is_opaque)
2515 }
2516 RawImageFormat::RGBA8 => {
2517 let (bytes, is_opaque) =
2518 Self::load_rgba8(pixels, expected_len, premultiplied_alpha)?;
2519 (bytes, RawImageFormat::BGRA8, is_opaque)
2520 }
2521 RawImageFormat::R16 => {
2522 let (bytes, is_opaque) = Self::load_r16(pixels, expected_len)?;
2523 (bytes, RawImageFormat::BGRA8, is_opaque)
2524 }
2525 RawImageFormat::RG16 => {
2526 let (bytes, is_opaque) = Self::load_rg16(pixels, expected_len)?;
2527 (bytes, RawImageFormat::BGRA8, is_opaque)
2528 }
2529 RawImageFormat::RGB16 => {
2530 let (bytes, is_opaque) = Self::load_rgb16(pixels, expected_len)?;
2531 (bytes, RawImageFormat::BGRA8, is_opaque)
2532 }
2533 RawImageFormat::RGBA16 => {
2534 let (bytes, is_opaque) =
2535 Self::load_rgba16(pixels, expected_len, premultiplied_alpha)?;
2536 (bytes, RawImageFormat::BGRA8, is_opaque)
2537 }
2538 RawImageFormat::BGR8 => {
2539 let (bytes, is_opaque) = Self::load_bgr8(pixels, expected_len)?;
2540 (bytes, RawImageFormat::BGRA8, is_opaque)
2541 }
2542 RawImageFormat::BGRA8 => {
2543 let (bytes, is_opaque) =
2544 Self::load_bgra8(pixels, expected_len, premultiplied_alpha)?;
2545 (bytes, RawImageFormat::BGRA8, is_opaque)
2546 }
2547 RawImageFormat::RGBF32 => {
2548 let (bytes, is_opaque) = Self::load_rgbf32(pixels, expected_len)?;
2549 (bytes, RawImageFormat::BGRA8, is_opaque)
2550 }
2551 RawImageFormat::RGBAF32 => {
2552 let (bytes, is_opaque) =
2553 Self::load_rgbaf32(pixels, expected_len, premultiplied_alpha)?;
2554 (bytes, RawImageFormat::BGRA8, is_opaque)
2555 }
2556 };
2557
2558 let image_data = ImageData::Raw(SharedRawImageData::new(bytes));
2559 let image_descriptor = ImageDescriptor {
2560 format: data_format,
2561 width,
2562 height,
2563 offset: 0,
2564 stride: None.into(),
2565 flags: ImageDescriptorFlags {
2566 is_opaque,
2567 allow_mipmaps: true,
2568 },
2569 };
2570
2571 Some((image_data, image_descriptor))
2572 }
2573
2574 fn load_r8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2578 let pixels = pixels.get_u8_vec()?;
2579
2580 if pixels.len() != expected_len {
2581 return None;
2582 }
2583
2584 Some((pixels, false))
2585 }
2586
2587 fn load_rg8(
2588 pixels: RawImageData,
2589 expected_len: usize,
2590 premultiplied_alpha: bool,
2591 ) -> Option<(U8Vec, bool)> {
2592 let pixels = pixels.get_u8_vec()?;
2593
2594 if pixels.len() != expected_len * TWO_CHANNELS {
2595 return None;
2596 }
2597
2598 let mut is_opaque = true;
2599 let mut px = vec![0; expected_len * FOUR_BPP];
2600
2601 for (pixel_index, greyalpha) in pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate() {
2603 let grey = greyalpha[0];
2604 let alpha = greyalpha[1];
2605
2606 if alpha != 255 {
2607 is_opaque = false;
2608 }
2609
2610 px[pixel_index * FOUR_BPP] = grey;
2611 px[(pixel_index * FOUR_BPP) + 1] = grey;
2612 px[(pixel_index * FOUR_BPP) + 2] = grey;
2613 px[(pixel_index * FOUR_BPP) + 3] = alpha;
2614
2615 if !premultiplied_alpha {
2616 premultiply_alpha(
2617 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2618 );
2619 }
2620 }
2621
2622 Some((px.into(), is_opaque))
2623 }
2624
2625 fn load_rgb8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2626 let pixels = pixels.get_u8_vec()?;
2627
2628 if pixels.len() != expected_len * THREE_CHANNELS {
2629 return None;
2630 }
2631
2632 let mut px = vec![0; expected_len * FOUR_BPP];
2633
2634 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2636 let red = rgb[0];
2637 let green = rgb[1];
2638 let blue = rgb[2];
2639
2640 px[pixel_index * FOUR_BPP] = blue;
2641 px[(pixel_index * FOUR_BPP) + 1] = green;
2642 px[(pixel_index * FOUR_BPP) + 2] = red;
2643 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2644 }
2645
2646 Some((px.into(), true))
2647 }
2648
2649 fn load_rgba8(
2650 pixels: RawImageData,
2651 expected_len: usize,
2652 premultiplied_alpha: bool,
2653 ) -> Option<(U8Vec, bool)> {
2654 let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
2655
2656 if pixels.len() != expected_len * FOUR_CHANNELS {
2657 return None;
2658 }
2659
2660 let mut is_opaque = true;
2661
2662 if premultiplied_alpha {
2665 for rgba in pixels.chunks_exact_mut(4) {
2666 let (r, gba) = rgba.split_first_mut()?;
2667 core::mem::swap(r, gba.get_mut(1)?);
2668 let a = rgba.get_mut(3)?;
2669 if *a != 255 {
2670 is_opaque = false;
2671 }
2672 }
2673 } else {
2674 for rgba in pixels.chunks_exact_mut(4) {
2675 let (r, gba) = rgba.split_first_mut()?;
2677 core::mem::swap(r, gba.get_mut(1)?);
2678 let a = rgba.get_mut(3)?;
2679 if *a != 255 {
2680 is_opaque = false;
2681 }
2682 premultiply_alpha(rgba); }
2684 }
2685
2686 Some((pixels.into(), is_opaque))
2687 }
2688
2689 fn load_r16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2690 let pixels = pixels.get_u16_vec()?;
2691
2692 if pixels.len() != expected_len {
2693 return None;
2694 }
2695
2696 let mut px = vec![0; expected_len * FOUR_BPP];
2697
2698 for (pixel_index, grey_u16) in pixels.as_ref().iter().enumerate() {
2700 let grey_u8 = normalize_u16(*grey_u16);
2701 px[pixel_index * FOUR_BPP] = grey_u8;
2702 px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
2703 px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
2704 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2705 }
2706
2707 Some((px.into(), true))
2708 }
2709
2710 fn load_rg16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2711 let pixels = pixels.get_u16_vec()?;
2712
2713 if pixels.len() != expected_len * TWO_CHANNELS {
2714 return None;
2715 }
2716
2717 let mut is_opaque = true;
2718 let mut px = vec![0; expected_len * FOUR_BPP];
2719
2720 for (pixel_index, greyalpha) in pixels.as_ref().chunks_exact(TWO_CHANNELS).enumerate() {
2722 let grey_u8 = normalize_u16(greyalpha[0]);
2723 let alpha_u8 = normalize_u16(greyalpha[1]);
2724
2725 if alpha_u8 != 255 {
2726 is_opaque = false;
2727 }
2728
2729 px[pixel_index * FOUR_BPP] = grey_u8;
2730 px[(pixel_index * FOUR_BPP) + 1] = grey_u8;
2731 px[(pixel_index * FOUR_BPP) + 2] = grey_u8;
2732 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2733 }
2734
2735 Some((px.into(), is_opaque))
2736 }
2737
2738 fn load_rgb16(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2739 let pixels = pixels.get_u16_vec()?;
2740
2741 if pixels.len() != expected_len * THREE_CHANNELS {
2742 return None;
2743 }
2744
2745 let mut px = vec![0; expected_len * FOUR_BPP];
2746
2747 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2749 let red_u8 = normalize_u16(rgb[0]);
2750 let green_u8 = normalize_u16(rgb[1]);
2751 let blue_u8 = normalize_u16(rgb[2]);
2752
2753 px[pixel_index * FOUR_BPP] = blue_u8;
2754 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2755 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2756 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2757 }
2758
2759 Some((px.into(), true))
2760 }
2761
2762 fn load_rgba16(
2763 pixels: RawImageData,
2764 expected_len: usize,
2765 premultiplied_alpha: bool,
2766 ) -> Option<(U8Vec, bool)> {
2767 let pixels = pixels.get_u16_vec()?;
2768
2769 if pixels.len() != expected_len * FOUR_CHANNELS {
2770 return None;
2771 }
2772
2773 let mut is_opaque = true;
2774 let mut px = vec![0; expected_len * FOUR_BPP];
2775
2776 if premultiplied_alpha {
2778 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2779 let red_u8 = normalize_u16(rgba[0]);
2780 let green_u8 = normalize_u16(rgba[1]);
2781 let blue_u8 = normalize_u16(rgba[2]);
2782 let alpha_u8 = normalize_u16(rgba[3]);
2783
2784 if alpha_u8 != 255 {
2785 is_opaque = false;
2786 }
2787
2788 px[pixel_index * FOUR_BPP] = blue_u8;
2789 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2790 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2791 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2792 }
2793 } else {
2794 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2795 let red_u8 = normalize_u16(rgba[0]);
2796 let green_u8 = normalize_u16(rgba[1]);
2797 let blue_u8 = normalize_u16(rgba[2]);
2798 let alpha_u8 = normalize_u16(rgba[3]);
2799
2800 if alpha_u8 != 255 {
2801 is_opaque = false;
2802 }
2803
2804 px[pixel_index * FOUR_BPP] = blue_u8;
2805 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2806 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2807 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2808 premultiply_alpha(
2809 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2810 );
2811 }
2812 }
2813
2814 Some((px.into(), is_opaque))
2815 }
2816
2817 fn load_bgr8(pixels: RawImageData, expected_len: usize) -> Option<(U8Vec, bool)> {
2818 let pixels = pixels.get_u8_vec()?;
2819
2820 if pixels.len() != expected_len * THREE_CHANNELS {
2821 return None;
2822 }
2823
2824 let mut px = vec![0; expected_len * FOUR_BPP];
2825
2826 for (pixel_index, bgr) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2828 let blue = bgr[0];
2829 let green = bgr[1];
2830 let red = bgr[2];
2831
2832 px[pixel_index * FOUR_BPP] = blue;
2833 px[(pixel_index * FOUR_BPP) + 1] = green;
2834 px[(pixel_index * FOUR_BPP) + 2] = red;
2835 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2836 }
2837
2838 Some((px.into(), true))
2839 }
2840
2841 fn load_bgra8(
2842 pixels: RawImageData,
2843 expected_len: usize,
2844 premultiplied_alpha: bool,
2845 ) -> Option<(U8Vec, bool)> {
2846 let mut is_opaque = true;
2847
2848 let bytes: U8Vec = if premultiplied_alpha {
2849 let pixels = pixels.get_u8_vec()?;
2851
2852 if pixels.len() != expected_len * FOUR_BPP {
2853 return None;
2854 }
2855
2856 is_opaque = pixels
2857 .as_ref()
2858 .chunks_exact(FOUR_CHANNELS)
2859 .all(|bgra| bgra[3] == 255);
2860
2861 pixels
2862 } else {
2863 let mut pixels: Vec<u8> = pixels.get_u8_vec()?.into_library_owned_vec();
2864
2865 if pixels.len() != expected_len * FOUR_BPP {
2866 return None;
2867 }
2868
2869 for bgra in pixels.chunks_exact_mut(FOUR_CHANNELS) {
2870 if bgra[3] != 255 {
2871 is_opaque = false;
2872 }
2873 premultiply_alpha(bgra);
2874 }
2875 pixels.into()
2876 };
2877
2878 Some((bytes, is_opaque))
2879 }
2880
2881 #[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)> {
2885 let pixels = pixels.get_f32_vec_ref()?;
2886
2887 if pixels.len() != expected_len * THREE_CHANNELS {
2888 return None;
2889 }
2890
2891 let mut px = vec![0; expected_len * FOUR_BPP];
2892
2893 for (pixel_index, rgb) in pixels.as_ref().chunks_exact(THREE_CHANNELS).enumerate() {
2895 let red_u8 = (rgb[0] * 255.0) as u8;
2896 let green_u8 = (rgb[1] * 255.0) as u8;
2897 let blue_u8 = (rgb[2] * 255.0) as u8;
2898
2899 px[pixel_index * FOUR_BPP] = blue_u8;
2900 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2901 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2902 px[(pixel_index * FOUR_BPP) + 3] = 0xff;
2903 }
2904
2905 Some((px.into(), true))
2906 }
2907
2908 #[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_sign_loss)] #[allow(clippy::needless_pass_by_value)] fn load_rgbaf32(
2912 pixels: RawImageData,
2913 expected_len: usize,
2914 premultiplied_alpha: bool,
2915 ) -> Option<(U8Vec, bool)> {
2916 let pixels = pixels.get_f32_vec_ref()?;
2917
2918 if pixels.len() != expected_len * FOUR_CHANNELS {
2919 return None;
2920 }
2921
2922 let mut is_opaque = true;
2923 let mut px = vec![0; expected_len * FOUR_BPP];
2924
2925 if premultiplied_alpha {
2927 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2928 let red_u8 = (rgba[0] * 255.0) as u8;
2929 let green_u8 = (rgba[1] * 255.0) as u8;
2930 let blue_u8 = (rgba[2] * 255.0) as u8;
2931 let alpha_u8 = (rgba[3] * 255.0) as u8;
2932
2933 if alpha_u8 != 255 {
2934 is_opaque = false;
2935 }
2936
2937 px[pixel_index * FOUR_BPP] = blue_u8;
2938 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2939 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2940 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2941 }
2942 } else {
2943 for (pixel_index, rgba) in pixels.as_ref().chunks_exact(FOUR_CHANNELS).enumerate() {
2944 let red_u8 = (rgba[0] * 255.0) as u8;
2945 let green_u8 = (rgba[1] * 255.0) as u8;
2946 let blue_u8 = (rgba[2] * 255.0) as u8;
2947 let alpha_u8 = (rgba[3] * 255.0) as u8;
2948
2949 if alpha_u8 != 255 {
2950 is_opaque = false;
2951 }
2952
2953 px[pixel_index * FOUR_BPP] = blue_u8;
2954 px[(pixel_index * FOUR_BPP) + 1] = green_u8;
2955 px[(pixel_index * FOUR_BPP) + 2] = red_u8;
2956 px[(pixel_index * FOUR_BPP) + 3] = alpha_u8;
2957 premultiply_alpha(
2958 &mut px[(pixel_index * FOUR_BPP)..((pixel_index * FOUR_BPP) + FOUR_BPP)],
2959 );
2960 }
2961 }
2962
2963 Some((px.into(), is_opaque))
2964 }
2965}
2966
2967impl_option!(
2968 RawImage,
2969 OptionRawImage,
2970 copy = false,
2971 [Debug, Clone, PartialEq, PartialOrd]
2972);
2973
2974#[must_use]
2975pub fn font_size_to_au(font_size: StyleFontSize) -> Au {
2976 Au::from_px(
2977 font_size
2978 .inner
2979 .to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE),
2980 )
2981}
2982
2983pub type FontInstanceFlags = u32;
2984
2985pub const FONT_INSTANCE_FLAG_SYNTHETIC_BOLD: u32 = 1 << 1;
2987pub const FONT_INSTANCE_FLAG_EMBEDDED_BITMAPS: u32 = 1 << 2;
2988pub const FONT_INSTANCE_FLAG_SUBPIXEL_BGR: u32 = 1 << 3;
2989pub const FONT_INSTANCE_FLAG_TRANSPOSE: u32 = 1 << 4;
2990pub const FONT_INSTANCE_FLAG_FLIP_X: u32 = 1 << 5;
2991pub const FONT_INSTANCE_FLAG_FLIP_Y: u32 = 1 << 6;
2992pub const FONT_INSTANCE_FLAG_SUBPIXEL_POSITION: u32 = 1 << 7;
2993
2994pub const FONT_INSTANCE_FLAG_FORCE_GDI: u32 = 1 << 16;
2996
2997pub const FONT_INSTANCE_FLAG_FONT_SMOOTHING: u32 = 1 << 16;
2999
3000pub const FONT_INSTANCE_FLAG_FORCE_AUTOHINT: u32 = 1 << 16;
3002pub const FONT_INSTANCE_FLAG_NO_AUTOHINT: u32 = 1 << 17;
3003pub const FONT_INSTANCE_FLAG_VERTICAL_LAYOUT: u32 = 1 << 18;
3004pub const FONT_INSTANCE_FLAG_LCD_VERTICAL: u32 = 1 << 19;
3005
3006#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3007pub struct GlyphOptions {
3008 pub render_mode: FontRenderMode,
3009 pub flags: FontInstanceFlags,
3010}
3011
3012#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3013pub enum FontRenderMode {
3014 Mono,
3015 Alpha,
3016 Subpixel,
3017}
3018
3019#[cfg(target_arch = "wasm32")]
3020#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3021pub struct FontInstancePlatformOptions {
3022 }
3024
3025#[cfg(target_os = "windows")]
3026#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3027pub struct FontInstancePlatformOptions {
3028 pub gamma: u16,
3029 pub contrast: u8,
3030 pub cleartype_level: u8,
3031}
3032
3033#[cfg(target_os = "macos")]
3034#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3035pub struct FontInstancePlatformOptions {
3036 pub unused: u32,
3037}
3038
3039#[cfg(target_os = "linux")]
3040#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3041pub struct FontInstancePlatformOptions {
3042 pub lcd_filter: FontLCDFilter,
3043 pub hinting: FontHinting,
3044}
3045
3046#[cfg(any(target_os = "android", target_os = "ios"))]
3050#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3051pub struct FontInstancePlatformOptions {
3052 pub unused: u32,
3053}
3054
3055#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3056pub enum FontHinting {
3057 None,
3058 Mono,
3059 Light,
3060 Normal,
3061 LCD,
3062}
3063
3064#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Default)]
3065pub enum FontLCDFilter {
3066 None,
3067 #[default]
3068 Default,
3069 Light,
3070 Legacy,
3071}
3072
3073#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3074pub struct FontInstanceOptions {
3075 pub render_mode: FontRenderMode,
3076 pub flags: FontInstanceFlags,
3077 pub bg_color: ColorU,
3078 pub synthetic_italics: SyntheticItalics,
3082}
3083
3084impl Default for FontInstanceOptions {
3085 fn default() -> Self {
3086 Self {
3087 render_mode: FontRenderMode::Subpixel,
3088 flags: 0,
3089 bg_color: ColorU::TRANSPARENT,
3090 synthetic_italics: SyntheticItalics::default(),
3091 }
3092 }
3093}
3094
3095#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Default)]
3096pub struct SyntheticItalics {
3097 pub angle: i16,
3098}
3099
3100#[derive(Debug)]
3106#[repr(C)]
3107pub struct SharedRawImageData {
3108 pub data: *const U8Vec,
3110 pub copies: *const AtomicUsize,
3112 pub run_destructor: bool,
3114}
3115
3116impl SharedRawImageData {
3117 #[must_use]
3119 pub fn new(data: U8Vec) -> Self {
3120 Self {
3121 data: Box::into_raw(Box::new(data)),
3122 copies: Box::into_raw(Box::new(AtomicUsize::new(1))),
3123 run_destructor: true,
3124 }
3125 }
3126
3127 #[must_use]
3129 pub fn as_ref(&self) -> &[u8] {
3130 unsafe { (*self.data).as_ref() }
3133 }
3134
3135 #[must_use]
3137 pub fn get_bytes(&self) -> &[u8] {
3138 self.as_ref()
3139 }
3140
3141 #[must_use]
3143 pub fn as_ptr(&self) -> *const u8 {
3144 unsafe { (*self.data).as_ref().as_ptr() }
3146 }
3147
3148 #[must_use]
3150 pub const fn len(&self) -> usize {
3151 unsafe { (*self.data).len() }
3153 }
3154
3155 #[must_use]
3157 pub const fn is_empty(&self) -> bool {
3158 self.len() == 0
3159 }
3160
3161 #[must_use]
3164 pub fn into_inner(self) -> Option<U8Vec> {
3165 unsafe {
3169 if self.copies.as_ref().map(|m| m.load(AtomicOrdering::SeqCst)) == Some(1) {
3170 let data = Box::from_raw(self.data.cast_mut());
3171 drop(Box::from_raw(self.copies.cast_mut()));
3172 core::mem::forget(self); Some(*data)
3174 } else {
3175 None
3176 }
3177 }
3178 }
3179}
3180
3181unsafe impl Send for SharedRawImageData {}
3184unsafe impl Sync for SharedRawImageData {}
3185
3186impl Clone for SharedRawImageData {
3187 fn clone(&self) -> Self {
3188 unsafe {
3191 self.copies
3192 .as_ref()
3193 .map(|m| m.fetch_add(1, AtomicOrdering::SeqCst));
3194 }
3195 Self {
3196 data: self.data,
3197 copies: self.copies,
3198 run_destructor: true,
3199 }
3200 }
3201}
3202
3203impl Drop for SharedRawImageData {
3204 fn drop(&mut self) {
3205 self.run_destructor = false;
3206 unsafe {
3210 let copies = (*self.copies).fetch_sub(1, AtomicOrdering::SeqCst);
3211 if copies == 1 {
3212 drop(Box::from_raw(self.data.cast_mut()));
3213 drop(Box::from_raw(self.copies.cast_mut()));
3214 }
3215 }
3216 }
3217}
3218
3219impl PartialEq for SharedRawImageData {
3220 fn eq(&self, rhs: &Self) -> bool {
3221 core::ptr::eq(self.data, rhs.data)
3222 }
3223}
3224
3225impl Eq for SharedRawImageData {}
3226
3227impl PartialOrd for SharedRawImageData {
3228 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
3229 Some(self.cmp(other))
3230 }
3231}
3232
3233impl Ord for SharedRawImageData {
3234 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
3235 (self.data as usize).cmp(&(other.data as usize))
3236 }
3237}
3238
3239impl Hash for SharedRawImageData {
3240 fn hash<H>(&self, state: &mut H)
3241 where
3242 H: Hasher,
3243 {
3244 (self.data as usize).hash(state);
3245 }
3246}
3247
3248#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3251#[repr(C, u8)]
3252pub enum ImageData {
3253 Raw(SharedRawImageData),
3256 External(ExternalImageData),
3259}
3260
3261#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
3263#[repr(C, u8)]
3264pub enum ExternalImageType {
3265 TextureHandle(ImageBufferKind),
3267 Buffer,
3269}
3270
3271#[repr(C)]
3275#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
3276pub struct ExternalImageId {
3277 pub inner: u64,
3278}
3279
3280static LAST_EXTERNAL_IMAGE_ID: AtomicUsize = AtomicUsize::new(0);
3281
3282impl Default for ExternalImageId {
3283 fn default() -> Self {
3284 Self::new()
3285 }
3286}
3287
3288impl ExternalImageId {
3289 pub fn new() -> Self {
3291 Self {
3292 inner: LAST_EXTERNAL_IMAGE_ID.fetch_add(1, AtomicOrdering::SeqCst) as u64,
3293 }
3294 }
3295}
3296
3297#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
3298#[repr(C, u8)]
3299pub enum GlyphOutlineOperation {
3300 MoveTo(OutlineMoveTo),
3301 LineTo(OutlineLineTo),
3302 QuadraticCurveTo(OutlineQuadTo),
3303 CubicCurveTo(OutlineCubicTo),
3304 ClosePath,
3305}
3306
3307impl_option!(
3308 GlyphOutlineOperation,
3309 OptionGlyphOutlineOperation,
3310 copy = false,
3311 [Debug, Clone, PartialEq, Eq, PartialOrd]
3312);
3313
3314#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
3316#[repr(C)]
3317pub struct OutlineMoveTo {
3318 pub x: i16,
3319 pub y: i16,
3320}
3321
3322#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
3324#[repr(C)]
3325pub struct OutlineLineTo {
3326 pub x: i16,
3327 pub y: i16,
3328}
3329
3330#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
3332#[repr(C)]
3333pub struct OutlineQuadTo {
3334 pub ctrl_1_x: i16,
3335 pub ctrl_1_y: i16,
3336 pub end_x: i16,
3337 pub end_y: i16,
3338}
3339
3340#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
3342#[repr(C)]
3343pub struct OutlineCubicTo {
3344 pub ctrl_1_x: i16,
3345 pub ctrl_1_y: i16,
3346 pub ctrl_2_x: i16,
3347 pub ctrl_2_y: i16,
3348 pub end_x: i16,
3349 pub end_y: i16,
3350}
3351
3352#[derive(Debug, Clone, PartialEq, PartialOrd)]
3353#[repr(C)]
3354pub struct GlyphOutline {
3355 pub operations: GlyphOutlineOperationVec,
3356}
3357
3358azul_css::impl_vec!(
3359 GlyphOutlineOperation,
3360 GlyphOutlineOperationVec,
3361 GlyphOutlineOperationVecDestructor,
3362 GlyphOutlineOperationVecDestructorType,
3363 GlyphOutlineOperationVecSlice,
3364 OptionGlyphOutlineOperation
3365);
3366azul_css::impl_vec_clone!(
3367 GlyphOutlineOperation,
3368 GlyphOutlineOperationVec,
3369 GlyphOutlineOperationVecDestructor
3370);
3371azul_css::impl_vec_debug!(GlyphOutlineOperation, GlyphOutlineOperationVec);
3372azul_css::impl_vec_partialord!(GlyphOutlineOperation, GlyphOutlineOperationVec);
3373azul_css::impl_vec_partialeq!(GlyphOutlineOperation, GlyphOutlineOperationVec);
3374
3375#[derive(Debug, Clone, Copy)]
3376#[repr(C)]
3377pub struct OwnedGlyphBoundingBox {
3378 pub max_x: i16,
3379 pub max_y: i16,
3380 pub min_x: i16,
3381 pub min_y: i16,
3382}
3383
3384#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
3386#[repr(C)]
3387pub enum ImageBufferKind {
3388 Texture2D = 0,
3390 TextureRect = 1,
3397 TextureExternal = 2,
3402}
3403
3404#[repr(C)]
3406#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
3407pub struct ExternalImageData {
3408 pub id: ExternalImageId,
3410 pub channel_index: u8,
3413 pub image_type: ExternalImageType,
3415}
3416
3417pub type TileSize = u16;
3418
3419#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
3420pub enum ImageDirtyRect {
3421 All,
3422 Partial(LayoutRect),
3423}
3424
3425#[derive(Debug, Clone, PartialEq, PartialOrd)]
3426pub enum ResourceUpdate {
3427 AddFont(AddFont),
3428 DeleteFont(FontKey),
3429 AddFontInstance(AddFontInstance),
3430 DeleteFontInstance(FontInstanceKey),
3431 AddImage(AddImage),
3432 UpdateImage(UpdateImage),
3433 DeleteImage(ImageKey),
3434}
3435
3436#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3437pub struct AddImage {
3438 pub key: ImageKey,
3439 pub descriptor: ImageDescriptor,
3440 pub data: ImageData,
3441 pub tiling: Option<TileSize>,
3442}
3443
3444#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
3445pub struct UpdateImage {
3446 pub key: ImageKey,
3447 pub descriptor: ImageDescriptor,
3448 pub data: ImageData,
3449 pub dirty_rect: ImageDirtyRect,
3450}
3451
3452#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
3455pub struct AddFont {
3456 pub key: FontKey,
3457 pub font: FontRef,
3458}
3459
3460impl fmt::Debug for AddFont {
3461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3462 write!(
3463 f,
3464 "AddFont {{ key: {:?}, font: {:?} }}",
3465 self.key, self.font
3466 )
3467 }
3468}
3469
3470#[derive(Debug, Clone, PartialEq, PartialOrd)]
3471pub struct AddFontInstance {
3472 pub key: FontInstanceKey,
3473 pub font_key: FontKey,
3474 pub glyph_size: (Au, DpiScaleFactor),
3475 pub options: Option<FontInstanceOptions>,
3476 pub platform_options: Option<FontInstancePlatformOptions>,
3477 pub variations: Vec<FontVariation>,
3478}
3479
3480#[repr(C)]
3481#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
3482pub struct FontVariation {
3483 pub tag: u32,
3484 pub value: f32,
3485}
3486
3487#[repr(C)]
3488#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
3489pub struct Epoch {
3490 inner: u32,
3491}
3492
3493impl fmt::Display for Epoch {
3494 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3495 write!(f, "{}", self.inner)
3496 }
3497}
3498
3499impl Default for Epoch {
3500 fn default() -> Self {
3501 Self::new()
3502 }
3503}
3504
3505impl Epoch {
3506 #[must_use]
3510 pub const fn new() -> Self {
3511 Self { inner: 0 }
3512 }
3513 #[must_use]
3514 pub const fn from(i: u32) -> Self {
3515 Self { inner: i }
3516 }
3517 #[must_use]
3518 pub const fn into_u32(&self) -> u32 {
3519 self.inner
3520 }
3521
3522 pub const fn increment(&mut self) {
3525 use core::u32;
3526 const MAX_ID: u32 = u32::MAX - 1;
3527 *self = match self.inner {
3528 MAX_ID => Self { inner: 0 },
3529 other => Self {
3530 inner: other.saturating_add(1),
3531 },
3532 };
3533 }
3534}
3535
3536#[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Eq, Ord)]
3538pub struct Au(pub i32);
3539
3540pub const AU_PER_PX: i32 = 60;
3541pub const MAX_AU: i32 = (1 << 30) - 1;
3542pub const MIN_AU: i32 = -(1 << 30) - 1;
3543
3544impl Au {
3545 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] #[must_use]
3547 pub fn from_px(px: f32) -> Self {
3548 let target_app_units = (px * AU_PER_PX as f32) as i32;
3549 Self(target_app_units.clamp(MIN_AU, MAX_AU))
3550 }
3551 #[allow(clippy::cast_precision_loss)] #[must_use]
3553 pub fn into_px(&self) -> f32 {
3554 self.0 as f32 / AU_PER_PX as f32
3555 }
3556}
3557
3558#[derive(Debug)]
3560pub enum AddFontMsg {
3561 Font(FontKey, StyleFontFamilyHash, FontRef),
3563 Instance(AddFontInstance, (Au, DpiScaleFactor)),
3564}
3565
3566impl AddFontMsg {
3567 #[must_use]
3568 pub fn into_resource_update(&self) -> ResourceUpdate {
3569 use self::AddFontMsg::{Font, Instance};
3570 match self {
3571 Font(font_key, _, font_ref) => ResourceUpdate::AddFont(AddFont {
3572 key: *font_key,
3573 font: font_ref.clone(),
3574 }),
3575 Instance(fi, _) => ResourceUpdate::AddFontInstance(fi.clone()),
3576 }
3577 }
3578}
3579
3580#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
3581pub enum DeleteFontMsg {
3582 Font(FontKey),
3583 Instance(FontInstanceKey, (Au, DpiScaleFactor)),
3584}
3585
3586impl DeleteFontMsg {
3587 #[must_use]
3588 pub const fn into_resource_update(&self) -> ResourceUpdate {
3589 use self::DeleteFontMsg::{Font, Instance};
3590 match self {
3591 Font(f) => ResourceUpdate::DeleteFont(*f),
3592 Instance(fi, _) => ResourceUpdate::DeleteFontInstance(*fi),
3593 }
3594 }
3595}
3596
3597#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
3598pub struct AddImageMsg(pub AddImage);
3599
3600impl AddImageMsg {
3601 #[must_use]
3602 pub fn into_resource_update(&self) -> ResourceUpdate {
3603 ResourceUpdate::AddImage(self.0.clone())
3604 }
3605}
3606
3607#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3608#[repr(C)]
3609pub struct LoadedFontSource {
3610 pub data: U8Vec,
3611 pub index: u32,
3612 pub load_outlines: bool,
3613}
3614
3615pub type LoadFontFn = fn(&StyleFontFamily, &FcFontCache) -> Option<LoadedFontSource>;
3617
3618pub type ParseFontFn = fn(LoadedFontSource) -> Option<FontRef>; pub type GlStoreImageFn = fn(DocumentId, Epoch, Texture, ExternalImageId);
3622
3623#[must_use]
3630pub fn texture_external_image_id(dom_id: DomId, node_id: NodeId) -> ExternalImageId {
3631 let dom = dom_id.inner as u64;
3632 let node = node_id.index() as u64;
3633 debug_assert!(u32::try_from(dom).is_ok(), "DomId exceeds 32-bit range");
3634 debug_assert!(u32::try_from(node).is_ok(), "NodeId exceeds 32-bit range");
3635 ExternalImageId {
3636 inner: (dom << 32) | (node & 0xFFFF_FFFF),
3637 }
3638}
3639
3640#[must_use]
3644pub const fn image_ref_hash_to_external_image_id(hash: ImageRefHash) -> ExternalImageId {
3645 ExternalImageId { inner: hash.inner }
3646}
3647
3648#[allow(clippy::too_many_lines)] pub fn build_add_font_resource_updates(
3658 renderer_resources: &mut RendererResources,
3659 dpi: DpiScaleFactor,
3660 fc_cache: &FcFontCache,
3661 id_namespace: IdNamespace,
3662 fonts_in_dom: &OrderedMap<ImmediateFontId, FastBTreeSet<Au>>,
3663 font_source_load_fn: LoadFontFn,
3664 parse_font_fn: ParseFontFn,
3665) -> Vec<(StyleFontFamilyHash, AddFontMsg)> {
3666 let mut resource_updates = Vec::new();
3667 let mut font_instances_added_this_frame = FastBTreeSet::new();
3668
3669 'outer: for (im_font_id, font_sizes) in fonts_in_dom {
3670 macro_rules! insert_font_instances {
3671 ($font_family_hash:expr, $font_key:expr, $font_size:expr) => {{
3672 let font_instance_key_exists = renderer_resources
3673 .currently_registered_fonts
3674 .get(&$font_key)
3675 .and_then(|(_, font_instances)| font_instances.get(&($font_size, dpi)))
3676 .is_some()
3677 || font_instances_added_this_frame.contains(&($font_key, ($font_size, dpi)));
3678
3679 if !font_instance_key_exists {
3680 let font_instance_key = FontInstanceKey::unique(id_namespace);
3681
3682 #[cfg(target_os = "windows")]
3684 let platform_options = FontInstancePlatformOptions {
3685 gamma: 300,
3686 contrast: 100,
3687 cleartype_level: 100,
3688 };
3689
3690 #[cfg(target_os = "linux")]
3691 let platform_options = FontInstancePlatformOptions {
3692 lcd_filter: FontLCDFilter::Default,
3693 hinting: FontHinting::Normal,
3694 };
3695
3696 #[cfg(target_os = "macos")]
3697 let platform_options = FontInstancePlatformOptions::default();
3698
3699 #[cfg(target_arch = "wasm32")]
3700 let platform_options = FontInstancePlatformOptions::default();
3701
3702 #[cfg(any(target_os = "android", target_os = "ios"))]
3703 let platform_options = FontInstancePlatformOptions::default();
3704
3705 let options = FontInstanceOptions {
3706 render_mode: FontRenderMode::Subpixel,
3707 flags: FONT_INSTANCE_FLAG_NO_AUTOHINT,
3708 ..Default::default()
3709 };
3710
3711 font_instances_added_this_frame.insert(($font_key, ($font_size, dpi)));
3712 resource_updates.push((
3713 $font_family_hash,
3714 AddFontMsg::Instance(
3715 AddFontInstance {
3716 key: font_instance_key,
3717 font_key: $font_key,
3718 glyph_size: ($font_size, dpi),
3719 options: Some(options),
3720 platform_options: Some(platform_options),
3721 variations: alloc::vec::Vec::new(),
3722 },
3723 ($font_size, dpi),
3724 ),
3725 ));
3726 }
3727 }};
3728 }
3729
3730 match im_font_id {
3731 ImmediateFontId::Resolved((font_family_hash, font_id)) => {
3732 for font_size in font_sizes {
3735 insert_font_instances!(*font_family_hash, *font_id, *font_size);
3736 }
3737 }
3738 ImmediateFontId::Unresolved(style_font_families) => {
3739 let mut font_family_hash = None;
3749 let font_families_hash = StyleFontFamiliesHash::new(style_font_families.as_ref());
3750
3751 'inner: for family in style_font_families.as_ref() {
3753 let current_family_hash = StyleFontFamilyHash::new(family);
3754
3755 if let Some(font_id) = renderer_resources.font_id_map.get(¤t_family_hash)
3756 {
3757 for font_size in font_sizes {
3759 insert_font_instances!(current_family_hash, *font_id, *font_size);
3760 }
3761 continue 'outer;
3762 }
3763
3764 let font_ref = match family {
3765 StyleFontFamily::Ref(r) => r.clone(), other => {
3767 let Some(font_data) = (font_source_load_fn)(other, fc_cache) else {
3769 continue 'inner;
3770 };
3771
3772 match (parse_font_fn)(font_data) {
3773 Some(s) => s,
3774 None => continue 'inner,
3775 }
3776 }
3777 };
3778
3779 font_family_hash = Some((current_family_hash, font_ref));
3781 break 'inner;
3782 }
3783
3784 let Some((font_family_hash, font_ref)) = font_family_hash else {
3786 continue 'outer;
3787 };
3788
3789 let font_key = FontKey::unique(id_namespace);
3791 let add_font_msg = AddFontMsg::Font(font_key, font_family_hash, font_ref);
3792
3793 renderer_resources
3794 .font_id_map
3795 .insert(font_family_hash, font_key);
3796 renderer_resources
3797 .font_families_map
3798 .insert(font_families_hash, font_family_hash);
3799 resource_updates.push((font_family_hash, add_font_msg));
3800
3801 for font_size in font_sizes {
3803 insert_font_instances!(font_family_hash, font_key, *font_size);
3804 }
3805 }
3806 }
3807 }
3808
3809 resource_updates
3810}
3811
3812#[allow(unused_variables)]
3828pub fn build_add_image_resource_updates(
3829 renderer_resources: &RendererResources,
3830 id_namespace: IdNamespace,
3831 epoch: Epoch,
3832 document_id: &DocumentId,
3833 images_in_dom: &FastBTreeSet<ImageRef>,
3834 insert_into_active_gl_textures: GlStoreImageFn,
3835) -> Vec<(ImageRefHash, AddImageMsg)> {
3836 images_in_dom
3837 .iter()
3838 .filter_map(|image_ref| {
3839 let image_ref_hash = image_ref_get_hash(image_ref);
3840
3841 if renderer_resources
3842 .currently_registered_images
3843 .contains_key(&image_ref_hash)
3844 {
3845 return None;
3846 }
3847
3848 match image_ref.get_data() {
3851 DecodedImage::Gl(texture) => {
3852 let descriptor = texture.get_descriptor();
3853 let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
3854 let external_image_id = image_ref_hash_to_external_image_id(image_ref_hash);
3858 (insert_into_active_gl_textures)(
3860 *document_id,
3861 epoch,
3862 texture.clone(),
3863 external_image_id,
3864 );
3865 Some((
3866 image_ref_hash,
3867 AddImageMsg(AddImage {
3868 key,
3869 data: ImageData::External(ExternalImageData {
3870 id: external_image_id,
3871 channel_index: 0,
3872 image_type: ExternalImageType::TextureHandle(
3873 ImageBufferKind::Texture2D,
3874 ),
3875 }),
3876 descriptor,
3877 tiling: None,
3878 }),
3879 ))
3880 }
3881 DecodedImage::Raw((descriptor, data)) => {
3882 let key = image_ref_hash_to_image_key(image_ref_hash, id_namespace);
3883 Some((
3884 image_ref_hash,
3885 AddImageMsg(AddImage {
3886 key,
3887 data: data.clone(), descriptor: *descriptor, tiling: None,
3891 }),
3892 ))
3893 }
3894 DecodedImage::NullImage { .. } | DecodedImage::Callback(_) => None,
3897 }
3898 })
3899 .collect()
3900}
3901
3902#[allow(clippy::needless_pass_by_value)] pub fn add_resources(
3909 renderer_resources: &mut RendererResources,
3910 all_resource_updates: &mut Vec<ResourceUpdate>,
3911 add_font_resources: Vec<(StyleFontFamilyHash, AddFontMsg)>,
3912 add_image_resources: Vec<(ImageRefHash, AddImageMsg)>,
3913) {
3914 all_resource_updates.extend(
3915 add_font_resources
3916 .iter()
3917 .map(|(_, f)| f.into_resource_update()),
3918 );
3919 all_resource_updates.extend(
3920 add_image_resources
3921 .iter()
3922 .map(|(_, i)| i.into_resource_update()),
3923 );
3924
3925 for (image_ref_hash, add_image_msg) in &add_image_resources {
3926 renderer_resources.currently_registered_images.insert(
3927 *image_ref_hash,
3928 ResolvedImage {
3929 key: add_image_msg.0.key,
3930 descriptor: add_image_msg.0.descriptor,
3931 },
3932 );
3933 renderer_resources
3936 .image_key_map
3937 .insert(add_image_msg.0.key, *image_ref_hash);
3938 }
3939
3940 for (_, add_font_msg) in add_font_resources {
3941 use self::AddFontMsg::{Font, Instance};
3942 match add_font_msg {
3943 Font(fk, font_family_hash, font_ref) => {
3944 renderer_resources
3945 .currently_registered_fonts
3946 .entry(fk)
3947 .or_insert_with(|| (font_ref.clone(), OrderedMap::default()));
3948
3949 renderer_resources
3951 .font_hash_map
3952 .insert(font_ref.get_hash(), fk);
3953 }
3954 Instance(fi, size) => {
3955 if let Some((_, instances)) = renderer_resources
3956 .currently_registered_fonts
3957 .get_mut(&fi.font_key)
3958 {
3959 instances.insert(size, fi.key);
3960 }
3961 }
3962 }
3963 }
3964}
3965
3966#[cfg(test)]
3967#[path = "resources_test.rs"]
3968mod resources_test;