1#![doc(
9 html_logo_url = "https://raw.githubusercontent.com/maps4print/azul/master/assets/images/azul_logo_full_min.svg.png",
10 html_favicon_url = "https://raw.githubusercontent.com/maps4print/azul/master/assets/images/favicon.ico"
11)]
12#![deny(unused_must_use)]
23#![warn(clippy::all)]
24#![cfg_attr(not(test), warn(
29 clippy::pedantic,
30 clippy::nursery,
31 clippy::cargo,
32 missing_debug_implementations,
35 missing_copy_implementations,
36 unreachable_pub,
37 unused_qualifications,
38 unused_lifetimes,
39 unused_import_braces,
40 unused_macro_rules,
41 unused_crate_dependencies,
42 meta_variable_misuse,
43 trivial_casts,
44 trivial_numeric_casts,
45 elided_lifetimes_in_paths,
46 single_use_lifetimes,
47 variant_size_differences,
48 non_ascii_idents,
49 unsafe_op_in_unsafe_fn,
50 let_underscore_drop,
51))]
52#![allow(
53 unknown_lints,
57 mismatched_lifetime_syntaxes, function_casts_as_integer, unused_imports,
61 unused_variables,
62 unused_mut,
63 dead_code,
64 clippy::too_many_arguments,
66 clippy::legacy_numeric_constants,
68 unexpected_cfgs, deprecated, clippy::multiple_crate_versions,
74)]
75
76#[macro_use]
77extern crate alloc;
78extern crate core;
79
80#[cfg(feature = "icons")]
85use brotli_decompressor as _;
86use lru as _;
88#[cfg(feature = "text_layout")]
91use unicode_normalization as _;
92#[cfg(feature = "xml")]
93use xmlwriter as _;
94
95#[cfg(feature = "web_lift")]
111#[inline]
112pub unsafe fn az_mark(_addr: u32, _val: u32) {
113 core::ptr::write_volatile(_addr as usize as *mut u32, _val);
115}
116#[cfg(not(feature = "web_lift"))]
124#[inline]
125pub const unsafe fn az_mark(_addr: u32, _val: u32) {}
126
127#[cfg(feature = "web_lift")]
136#[inline]
137#[must_use] pub unsafe fn az_mark_read(_addr: u32) -> u32 {
138 core::ptr::read_volatile(_addr as usize as *const u32)
140}
141#[cfg(not(feature = "web_lift"))]
148#[inline]
149#[must_use] pub const unsafe fn az_mark_read(_addr: u32) -> u32 {
150 0
151}
152
153pub mod font_traits;
155pub mod probe;
158#[cfg(feature = "image_decoding")]
160pub mod image;
161#[cfg(feature = "text_layout")]
163#[allow(private_interfaces)]
165pub mod managers;
166#[cfg(feature = "text_layout")]
168#[allow(
172 dropping_references,
173 private_interfaces,
174 unreachable_patterns,
175 unused_parens,
176 unused_doc_comments,
177 unused_assignments
178)]
179pub mod solver3;
180
181#[cfg(feature = "strfmt")]
183pub mod fmt;
184#[cfg(feature = "strfmt")]
185pub use fmt::{FmtArg, FmtArgVec, FmtArgVecDestructor, FmtValue, fmt_string};
186
187#[cfg(feature = "widgets")]
189#[allow(unused_assignments, improper_ctypes_definitions)]
192pub mod widgets;
193
194#[cfg(feature = "extra")]
196pub mod desktop;
197
198#[cfg(any(
200 feature = "icu",
201 all(target_os = "macos", feature = "icu_macos"),
202 all(target_os = "windows", feature = "icu_windows"),
203))]
204pub mod icu;
205#[cfg(any(
206 feature = "icu",
207 all(target_os = "macos", feature = "icu_macos"),
208 all(target_os = "windows", feature = "icu_windows"),
209))]
210pub use icu::{
211 DateTimeFieldSet, FormatLength, IcuDate, IcuDateTime, IcuError,
212 IcuLocalizer, IcuLocalizerHandle, IcuResult, IcuStringVec, IcuTime,
213 LayoutCallbackInfoIcuExt, ListType, PluralCategory,
214};
215
216#[cfg(feature = "fluent")]
218pub mod fluent;
219#[cfg(feature = "fluent")]
220pub use fluent::{
221 check_fluent_syntax, check_fluent_syntax_bytes, create_fluent_zip,
222 create_fluent_zip_from_strings, export_to_zip, FluentError,
223 FluentLanguageInfo, FluentLanguageInfoVec, FluentLoadError, FluentLoadErrorVec,
224 FluentLocalizerHandle, FluentSyntaxCheckResult,
225 FluentZipLoadResult,
226};
227
228pub use azul_core::url;
233pub use azul_core::url::{Url, UrlParseError, ResultUrlUrlParseError};
234
235#[allow(unused_doc_comments, clippy::should_implement_trait)]
240pub mod file;
241pub use file::{
242 dir_create, dir_create_all, dir_list, dir_delete, dir_delete_all,
243 file_append, file_copy, path_exists, file_metadata, file_read, file_read_string,
244 file_delete, file_rename, file_write, file_write_string,
245 path_canonicalize, path_extension, path_file_name, path_is_dir, path_is_file,
246 path_join, path_parent, temp_dir,
247 DirEntry, DirEntryVec, DirEntryVecDestructor, DirEntryVecDestructorType,
248 FileError, FileErrorKind, FileMetadata, FilePath, OptionFilePath,
249};
250
251pub mod http;
255pub use http::{
256 download_bytes, download_bytes_with_config, http_get,
257 http_get_with_config, is_url_reachable, HttpError, HttpHeader,
258 HttpRequestConfig, HttpResponse, HttpResponseTooLargeError, HttpResult,
259 HttpStatusError,
260};
261
262#[cfg(feature = "json")]
264pub mod json;
265#[cfg(feature = "json")]
266pub use json::{
267 json_parse, json_stringify,
268 Json, JsonInternal, JsonKeyValue, JsonKeyValueVec, JsonKeyValueVecDestructor, JsonKeyValueVecDestructorType,
269 JsonParseError, JsonType, JsonVec,
270 ResultJsonJsonParseError, OptionJson, OptionJsonVec, OptionJsonKeyValueVec,
271};
272
273#[cfg(feature = "zip")]
275pub mod zip;
276#[cfg(feature = "zip")]
277pub use zip::{
278 zip_create, zip_create_from_files, zip_extract_all, zip_list_contents,
279 ZipFile, ZipFileEntry, ZipFileEntryVec, ZipPathEntry, ZipPathEntryVec,
280 ZipReadConfig, ZipWriteConfig, ZipReadError, ZipWriteError,
281};
282
283pub mod icon;
285pub use icon::{
286 default_icon_resolver,
288 ImageIconData, FontIconData,
290 register_image_icon,
292 register_font_icon,
293 register_icons_from_zip,
294 create_default_icon_provider,
295 register_material_icons,
296 register_embedded_material_icons,
297};
298pub use azul_core::icon::{
300 IconProviderHandle, IconResolverCallbackType,
301 resolve_icons_in_styled_dom, OptionIconProviderHandle,
302};
303
304#[cfg(feature = "text_layout")]
306pub mod callbacks;
307#[cfg(feature = "cpurender")]
309#[allow(clippy::type_complexity)]
311pub mod cpurender;
312#[cfg(feature = "cpurender")]
314pub mod glyph_cache;
315#[cfg(feature = "text_layout")]
317pub mod default_actions;
318#[cfg(feature = "text_layout")]
320pub mod event_determination;
321#[cfg(feature = "text_layout")]
323#[allow(clippy::type_complexity)]
325pub mod font;
326
327#[cfg(feature = "text_layout")]
331pub mod headless;
332#[cfg(feature = "text_layout")]
334pub use allsorts::subset::CmapTarget;
335#[cfg(feature = "text_layout")]
336pub use font::parsed::{
337 FontParseWarning, FontParseWarningSeverity, FontType, OwnedGlyph, ParsedFont, PdfFontMetrics,
338 SubsetFont,
339};
340#[cfg(feature = "text_layout_hyphenation")]
342pub use hyphenation;
343#[cfg(feature = "text_layout")]
345pub mod hit_test;
346pub use azul_core::paged;
350#[cfg(feature = "text_layout")]
352#[allow(private_interfaces, unused_labels, clippy::type_complexity)]
355pub mod text3;
356#[cfg(feature = "text_layout")]
358pub mod thread;
359#[cfg(feature = "text_layout")]
361#[allow(clippy::non_canonical_partial_ord_impl)]
363pub mod timer;
364#[cfg(feature = "text_layout")]
366pub mod scroll_timer;
367#[cfg(feature = "text_layout")]
369#[allow(unused_parens)]
371pub mod window;
372#[cfg(feature = "text_layout")]
374pub mod window_state;
375#[cfg(feature = "xml")]
377#[allow(unused_assignments)]
379pub mod xml;
380
381pub use paged::PageMargins;
384#[cfg(feature = "text_layout")]
385pub use hit_test::{CursorTypeHitTest, FullHitTest};
386#[cfg(feature = "text_layout")]
387pub use solver3::cache::LayoutCache as Solver3LayoutCache;
388#[cfg(feature = "text_layout")]
389pub use solver3::display_list::DisplayList as DisplayList3;
390#[cfg(feature = "text_layout")]
391pub use solver3::layout_document;
392#[cfg(feature = "text_layout")]
393pub use solver3::paged_layout::layout_document_paged;
394#[cfg(feature = "text_layout")]
395pub use solver3::{LayoutContext, LayoutError, Result as LayoutResult3};
396#[cfg(feature = "text_layout")]
397pub use text3::cache::{FontContext, FontManager, TextShapingCache};
398#[cfg(feature = "text_layout")]
402pub use text3::cache::TextShapingCache as TextLayoutCache;
403#[cfg(feature = "font_async_registry")]
404pub use rust_fontconfig::registry::FcFontRegistry;
405#[cfg(feature = "text_layout")]
406pub use window::{CursorBlinkTimerAction, LayoutWindow, ScrollbarDragState, TooltipTimerAction};
407#[cfg(feature = "text_layout")]
408pub use managers::text_input::{PendingTextEdit, OptionPendingTextEdit};
409
410#[cfg(feature = "text_layout")]
411#[allow(clippy::needless_pass_by_value)]
416pub fn parse_font_fn(
417 source: azul_core::resources::LoadedFontSource,
418) -> Option<azul_css::props::basic::FontRef> {
419 use crate::font::parsed::ParsedFont;
420
421 ParsedFont::from_bytes(
422 source.data.as_ref(),
423 source.index as usize,
424 &mut Vec::new(), )
426 .map(parsed_font_to_font_ref)
427}
428
429#[cfg(feature = "text_layout")]
430pub fn parsed_font_to_font_ref(
433 parsed_font: ParsedFont,
434) -> azul_css::props::basic::FontRef {
435 use core::ffi::c_void;
436
437 extern "C" fn parsed_font_destructor(ptr: *mut c_void) {
438 unsafe {
439 drop(Box::from_raw(ptr.cast::<ParsedFont>()));
440 }
441 }
442
443 let boxed = Box::new(parsed_font);
444 let raw_ptr = Box::into_raw(boxed) as *const c_void;
445 azul_css::props::basic::FontRef::new(raw_ptr, parsed_font_destructor)
446}
447
448#[cfg(feature = "text_layout")]
449#[must_use] pub const fn font_ref_to_parsed_font(
455 font_ref: &azul_css::props::basic::FontRef,
456) -> &ParsedFont {
457 unsafe { &*font_ref.parsed.cast::<ParsedFont>() }
460}
461
462#[cfg(test)]
463mod autotest_generated {
464 use super::*;
478
479 #[cfg(not(feature = "web_lift"))]
488 #[test]
489 fn az_mark_read_is_zero_for_every_boundary_address() {
490 let addresses = [
491 0u32,
492 1,
493 0x3_FFFF, 0x4_0000, 0x6_0758, 0xF_0000, 0xF_0001, i32::MIN as u32, (-1i32) as u32, u32::MAX - 1,
501 u32::MAX,
502 u32::MAX.wrapping_add(1), ];
504 for addr in addresses {
505 assert_eq!(unsafe { az_mark_read(addr) }, 0, "az_mark_read(0x{addr:x})");
506 }
507 }
508
509 #[cfg(not(feature = "web_lift"))]
512 #[test]
513 fn az_mark_read_sweeps_the_whole_address_space_as_zero() {
514 for addr in (0u32..=u32::MAX).step_by(1 << 24) {
515 assert_eq!(unsafe { az_mark_read(addr) }, 0);
516 }
517 }
518
519 #[cfg(not(feature = "web_lift"))]
522 #[test]
523 fn az_mark_writes_are_unobservable_without_web_lift() {
524 let addresses = [0u32, 0x4_0000, 0x6_0758, 0xF_0000, u32::MAX];
525 let values = [0u32, 1, u32::MAX / 2, u32::MAX - 1, u32::MAX, i32::MIN as u32];
526 for addr in addresses {
527 for val in values {
528 unsafe { az_mark(addr, val) };
529 assert_eq!(
530 unsafe { az_mark_read(addr) },
531 0,
532 "az_mark(0x{addr:x}, {val}) must not be observable"
533 );
534 }
535 }
536 for _ in 0..1_000 {
538 unsafe { az_mark(0x6_0758, u32::MAX) };
539 }
540 assert_eq!(unsafe { az_mark_read(0x6_0758) }, 0);
541 }
542
543 #[cfg(not(feature = "web_lift"))]
547 #[test]
548 fn az_mark_no_op_variants_are_const_evaluable() {
549 const _WRITE_MIN: () = unsafe { az_mark(0, 0) };
550 const _WRITE_MAX: () = unsafe { az_mark(u32::MAX, u32::MAX) };
551 const READ_ZERO: u32 = unsafe { az_mark_read(0) };
552 const READ_MAX: u32 = unsafe { az_mark_read(u32::MAX) };
553 assert_eq!(READ_ZERO, 0);
554 assert_eq!(READ_MAX, 0);
555 }
556
557 #[cfg(feature = "text_layout")]
563 const MOCK_MONO: &[u8] = crate::text3::mock_fonts::MOCK_MONO_TTF;
564
565 #[cfg(feature = "text_layout")]
566 fn loaded_source(bytes: Vec<u8>, index: u32) -> azul_core::resources::LoadedFontSource {
567 azul_core::resources::LoadedFontSource {
568 data: azul_css::U8Vec::from_vec(bytes),
569 index,
570 load_outlines: true,
571 }
572 }
573
574 #[cfg(feature = "text_layout")]
575 fn parse_mock() -> ParsedFont {
576 ParsedFont::from_bytes(MOCK_MONO, 0, &mut Vec::new())
577 .expect("Azul Mock Mono must parse (positive control)")
578 }
579
580 #[cfg(feature = "text_layout")]
587 #[test]
588 fn parse_font_fn_rejects_malformed_input() {
589 let cases: Vec<(&str, Vec<u8>)> = vec![
590 ("empty", Vec::new()),
591 ("single_nul", vec![0u8]),
592 ("whitespace_only", b" \t\n".to_vec()),
593 ("garbage", (0u8..=255).cycle().take(4096).collect()),
594 ("invalid_utf8", vec![0xFF, 0xFE, 0x00]),
595 ("sfnt_magic_only", vec![0x00, 0x01, 0x00, 0x00]),
596 ("header_only", MOCK_MONO[..12].to_vec()),
597 ("truncated_font", MOCK_MONO[..64].to_vec()),
598 ("half_a_font", MOCK_MONO[..MOCK_MONO.len() / 2].to_vec()),
599 ("unicode_emoji", "\u{1F600}\u{1F600}".repeat(1_000).into_bytes()),
600 ("combining_marks", "e\u{0301}".repeat(10_000).into_bytes()),
601 ("nested_brackets", vec![b'['; 10_000]),
602 ("boundary_numbers", b"0 -0 9223372036854775807 NaN inf -inf 1e309".to_vec()),
603 ("leading_junk_then_font", {
604 let mut v = b"garbage".to_vec();
605 v.extend_from_slice(MOCK_MONO);
606 v
607 }),
608 ];
609 for (name, bytes) in cases {
610 assert!(
611 parse_font_fn(loaded_source(bytes, 0)).is_none(),
612 "{name} must not parse into a FontRef"
613 );
614 }
615 }
616
617 #[cfg(feature = "text_layout")]
620 #[test]
621 fn parse_font_fn_survives_extremely_long_input() {
622 assert!(parse_font_fn(loaded_source(vec![0u8; 1_000_000], 0)).is_none());
623 assert!(parse_font_fn(loaded_source(vec![b'a'; 1_000_000], 0)).is_none());
624 let mut ttcf = b"ttcf".to_vec();
626 ttcf.extend_from_slice(&vec![0xABu8; 1_000_000]);
627 assert!(parse_font_fn(loaded_source(ttcf, 0)).is_none());
628 }
629
630 #[cfg(feature = "text_layout")]
634 #[test]
635 fn parse_font_fn_parses_the_positive_control() {
636 let font_ref = parse_font_fn(loaded_source(MOCK_MONO.to_vec(), 0))
637 .expect("the positive control must parse");
638 let parsed = font_ref_to_parsed_font(&font_ref);
639
640 assert_eq!(parsed.num_glyphs(), 96);
641 assert_eq!(parsed.num_glyphs(), parsed.maxp_table.num_glyphs);
642 assert_eq!(parsed.font_metrics.units_per_em, 1000);
643 assert!(parsed.font_metrics.ascent > 0.0);
644 assert!(parsed.font_metrics.descent <= 0.0);
645 assert!(parsed.font_metrics.ascent.is_finite());
646 assert!(parsed.font_metrics.descent.is_finite());
647 assert!(parsed.font_metrics.line_gap.is_finite());
648 assert_eq!(parsed.font_type, FontType::TrueType);
649 assert_eq!(parsed.original_index, 0);
650 assert!(parsed.cmap_subtable.is_some());
651 assert_eq!(parsed.hash, parse_mock().hash);
652 }
653
654 #[cfg(feature = "text_layout")]
658 #[test]
659 fn parse_font_fn_ignores_the_load_outlines_flag() {
660 let with = azul_core::resources::LoadedFontSource {
661 data: azul_css::U8Vec::from_vec(MOCK_MONO.to_vec()),
662 index: 0,
663 load_outlines: true,
664 };
665 let without = azul_core::resources::LoadedFontSource {
666 data: azul_css::U8Vec::from_vec(MOCK_MONO.to_vec()),
667 index: 0,
668 load_outlines: false,
669 };
670 let a = parse_font_fn(with).expect("parses with outlines");
671 let b = parse_font_fn(without).expect("parses without outlines");
672 let (pa, pb) = (font_ref_to_parsed_font(&a), font_ref_to_parsed_font(&b));
673 assert_eq!(pa.hash, pb.hash);
674 assert_eq!(pa.num_glyphs(), pb.num_glyphs());
675 assert_eq!(pa.pdf_font_metrics, pb.pdf_font_metrics);
676 }
677
678 #[cfg(feature = "text_layout")]
682 #[test]
683 fn parse_font_fn_with_an_out_of_range_index_is_deterministic() {
684 let baseline = parse_mock();
685 for index in [1u32, 2, 0x7FFF_FFFF, u32::MAX - 1, u32::MAX] {
686 if let Some(font_ref) = parse_font_fn(loaded_source(MOCK_MONO.to_vec(), index)) {
687 let parsed = font_ref_to_parsed_font(&font_ref);
688 assert_eq!(
689 parsed.num_glyphs(),
690 baseline.num_glyphs(),
691 "index {index} must not conjure a different face"
692 );
693 assert_eq!(parsed.original_index, index as usize);
694 }
695 }
696 }
697
698 #[cfg(feature = "text_layout")]
701 #[test]
702 fn parse_font_fn_failure_path_is_repeatable() {
703 for _ in 0..200 {
704 assert!(parse_font_fn(loaded_source(Vec::new(), 0)).is_none());
705 assert!(parse_font_fn(loaded_source(vec![0xFF; 3], u32::MAX)).is_none());
706 }
707 assert!(parse_font_fn(loaded_source(MOCK_MONO.to_vec(), 0)).is_some());
709 }
710
711 #[cfg(feature = "text_layout")]
715 #[test]
716 fn parse_font_fn_mints_a_fresh_identity_per_call() {
717 let a = parse_font_fn(loaded_source(MOCK_MONO.to_vec(), 0)).expect("parses");
718 let b = parse_font_fn(loaded_source(MOCK_MONO.to_vec(), 0)).expect("parses");
719 assert_ne!(a, b, "two parses of the same bytes are two distinct handles");
720 assert_ne!(a.id, b.id);
721 assert!(b > a, "ids are monotonically assigned");
722 assert_eq!(
724 font_ref_to_parsed_font(&a).hash,
725 font_ref_to_parsed_font(&b).hash
726 );
727 }
728
729 #[cfg(feature = "text_layout")]
736 #[test]
737 fn parsed_font_font_ref_round_trip_preserves_the_face() {
738 let original = parse_mock();
739 let expected_hash = original.hash;
740 let expected_glyphs = original.num_glyphs();
741 let expected_metrics = original.pdf_font_metrics;
742 let expected_upem = original.font_metrics.units_per_em;
743 let expected_ascent = original.font_metrics.ascent;
744 let expected_type = original.font_type.clone();
745 let expected_index = original.original_index;
746
747 let font_ref = parsed_font_to_font_ref(original);
748 let decoded = font_ref_to_parsed_font(&font_ref);
749
750 assert_eq!(decoded.hash, expected_hash);
751 assert_eq!(decoded.num_glyphs(), expected_glyphs);
752 assert_eq!(decoded.pdf_font_metrics, expected_metrics);
753 assert_eq!(decoded.font_metrics.units_per_em, expected_upem);
754 assert!((decoded.font_metrics.ascent - expected_ascent).abs() < f32::EPSILON);
755 assert_eq!(decoded.font_type, expected_type);
756 assert_eq!(decoded.original_index, expected_index);
757 }
758
759 #[cfg(feature = "text_layout")]
762 #[test]
763 fn parsed_font_to_font_ref_handle_invariants() {
764 use core::sync::atomic::Ordering as AtomicOrdering;
765
766 let font_ref = parsed_font_to_font_ref(parse_mock());
767 assert!(!font_ref.parsed.is_null());
768 assert!(!font_ref.copies.is_null());
769 assert!(font_ref.run_destructor);
770 assert_ne!(font_ref.id, 0, "id 0 is reserved for un-initialised handles");
771 assert_eq!(unsafe { (*font_ref.copies).load(AtomicOrdering::SeqCst) }, 1);
772 assert_eq!(font_ref.get_parsed(), font_ref.parsed);
773 }
774
775 #[cfg(feature = "text_layout")]
778 #[test]
779 fn font_ref_to_parsed_font_is_a_stable_reborrow() {
780 use core::ffi::c_void;
781
782 let font_ref = parsed_font_to_font_ref(parse_mock());
783 let first: *const ParsedFont = font_ref_to_parsed_font(&font_ref);
784 let second: *const ParsedFont = font_ref_to_parsed_font(&font_ref);
785 assert!(core::ptr::eq(first, second), "reborrow must be stable");
786 assert!(core::ptr::eq(first.cast::<c_void>(), font_ref.get_parsed()));
787 }
788
789 #[cfg(feature = "text_layout")]
793 #[test]
794 fn cloning_a_font_ref_shares_the_face_and_the_drop_is_refcounted() {
795 use core::sync::atomic::Ordering as AtomicOrdering;
796
797 let original = parsed_font_to_font_ref(parse_mock());
798 let expected_hash = font_ref_to_parsed_font(&original).hash;
799
800 let clone = original.clone();
801 assert_eq!(clone.id, original.id, "a clone is the same font");
802 assert_eq!(clone, original);
803 assert!(core::ptr::eq(clone.parsed, original.parsed));
804 assert_eq!(unsafe { (*original.copies).load(AtomicOrdering::SeqCst) }, 2);
805
806 drop(clone);
807 assert_eq!(unsafe { (*original.copies).load(AtomicOrdering::SeqCst) }, 1);
808 assert_eq!(
809 font_ref_to_parsed_font(&original).hash,
810 expected_hash,
811 "the face must survive its clone being dropped"
812 );
813 assert_eq!(font_ref_to_parsed_font(&original).num_glyphs(), 96);
814 }
815
816 #[cfg(feature = "text_layout")]
820 #[test]
821 fn font_ref_clone_drop_cycles_do_not_double_free() {
822 use core::sync::atomic::Ordering as AtomicOrdering;
823
824 let original = parsed_font_to_font_ref(parse_mock());
825 let expected_hash = font_ref_to_parsed_font(&original).hash;
826
827 for _ in 0..1_000 {
828 let c = original.clone();
829 assert_eq!(font_ref_to_parsed_font(&c).hash, expected_hash);
830 }
831
832 let batch: Vec<_> = (0..1_000).map(|_| original.clone()).collect();
833 assert_eq!(
834 unsafe { (*original.copies).load(AtomicOrdering::SeqCst) },
835 1_001
836 );
837 drop(batch);
838 assert_eq!(unsafe { (*original.copies).load(AtomicOrdering::SeqCst) }, 1);
839 assert_eq!(font_ref_to_parsed_font(&original).hash, expected_hash);
840 }
841
842 #[cfg(feature = "text_layout")]
845 #[test]
846 fn font_ref_identity_is_per_handle_not_per_content() {
847 use std::collections::{BTreeSet, HashSet};
848
849 let a = parsed_font_to_font_ref(parse_mock());
850 let b = parsed_font_to_font_ref(parse_mock());
851 assert_ne!(a, b);
852 assert!(a < b, "ids are monotonically assigned, so a precedes b");
853 assert_eq!(
854 font_ref_to_parsed_font(&a).hash,
855 font_ref_to_parsed_font(&b).hash,
856 "…even though the content hash is the same"
857 );
858
859 let set: HashSet<_> = vec![a.clone(), a.clone(), a.clone(), b.clone()]
860 .into_iter()
861 .collect();
862 assert_eq!(set.len(), 2, "clones dedup, distinct handles do not");
863
864 let ordered: BTreeSet<_> = vec![b.clone(), a.clone(), b.clone()].into_iter().collect();
865 assert_eq!(ordered.len(), 2);
866 assert_eq!(ordered.iter().next(), Some(&a));
867 }
868
869 #[cfg(feature = "text_layout")]
872 #[test]
873 fn many_font_refs_do_not_alias_each_other() {
874 let refs: Vec<_> = (0..16).map(|_| parsed_font_to_font_ref(parse_mock())).collect();
875 for (i, a) in refs.iter().enumerate() {
876 assert_eq!(font_ref_to_parsed_font(a).num_glyphs(), 96);
877 for b in refs.iter().skip(i + 1) {
878 assert!(
879 !core::ptr::eq(a.parsed, b.parsed),
880 "independently boxed faces must not share a pointer"
881 );
882 assert_ne!(a.id, b.id);
883 }
884 }
885 }
886}