Skip to main content

motion_core/asset/
environment.rs

1//! Immutable, type-erased content environments carried by core scene contracts.
2
3use crate::sync::MutexExt;
4use std::{
5    any::Any,
6    fmt,
7    sync::{Arc, Mutex},
8};
9
10/// Explicit font-source observations shared by rendering workers for one export.
11#[derive(Clone, Default, Debug)]
12pub struct FontUsage(Arc<Mutex<Vec<FontIdentity>>>);
13
14impl FontUsage {
15    /// Records a selected face once, including when its outline came from a cache.
16    pub fn record(&self, font: FontIdentity) {
17        let mut fonts = self.0.lock_unpoisoned();
18        if !fonts.contains(&font) {
19            fonts.push(font);
20        }
21    }
22
23    /// Returns stable face ordering independent of worker completion order.
24    pub fn fonts(&self) -> Vec<FontIdentity> {
25        let mut fonts = self.0.lock_unpoisoned().clone();
26        fonts.sort_by(|left, right| {
27            (&left.name, left.file_hash).cmp(&(&right.name, right.file_hash))
28        });
29        fonts
30    }
31}
32
33/// A frozen content environment with an explicit cache identity.
34/// The content package owns its concrete font implementation; core only carries
35/// its immutable snapshot and never initializes a font engine.
36#[derive(Clone)]
37pub struct FontEnvironment {
38    value: Arc<dyn Any + Send + Sync>,
39    identity: u64,
40    usage: Option<Arc<dyn Fn() -> Vec<FontIdentity> + Send + Sync>>,
41}
42
43impl std::panic::RefUnwindSafe for FontEnvironment {}
44
45impl std::panic::UnwindSafe for FontEnvironment {}
46
47impl From<&FontEnvironment> for FontEnvironment {
48    fn from(value: &FontEnvironment) -> Self {
49        value.clone()
50    }
51}
52
53impl FontEnvironment {
54    /// Captures an immutable environment under its owning implementation's identity.
55    pub fn new<T: Any + Send + Sync>(value: T, identity: u64) -> Self {
56        Self {
57            value: Arc::new(value),
58            identity,
59            usage: None,
60        }
61    }
62
63    /// Stable identity of this immutable environment snapshot.
64    pub fn snapshot_id(&self) -> u64 {
65        self.identity
66    }
67
68    /// Attaches explicit usage reporting without exposing the content engine to core.
69    pub fn with_usage(
70        mut self,
71        usage: impl Fn() -> Vec<FontIdentity> + Send + Sync + 'static,
72    ) -> Self {
73        self.usage = Some(Arc::new(usage));
74        self
75    }
76
77    /// Faces actually selected from this shared environment, including source hashes.
78    pub fn used_fonts(&self) -> Vec<FontIdentity> {
79        self.usage.as_ref().map_or_else(Vec::new, |usage| usage())
80    }
81
82    /// Resolves an environment only in the package that owns its concrete type.
83    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
84        self.value.downcast_ref()
85    }
86}
87
88impl fmt::Debug for FontEnvironment {
89    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
90        formatter
91            .debug_struct("FontEnvironment")
92            .field("snapshot", &self.identity)
93            .finish_non_exhaustive()
94    }
95}
96
97/// Identity of one parsed font face, in primary-then-fallback order.
98/// Hashes identify the font source, not a cryptographic checksum or a promise of
99/// identical rasterization across different text-engine versions/platforms.
100#[derive(Clone, Debug, PartialEq, Eq)]
101pub struct FontIdentity {
102    /// Face name when present in the font's metadata.
103    pub name: Option<String>,
104    /// Non-cryptographic font-source hash supplied by the text engine.
105    pub file_hash: u64,
106}