1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
//! Theme ladder L3's CoreText half, shared by BOTH Apple arms (iOS's
//! `crate::apple` and macOS's `crate::appkit`): mirrors
//! `crate::android::fonts`'s "resolve the embedded Glyph bytes to a real,
//! process-cached platform font object, once" shape, but via CoreText rather
//! than a cache-file write + `Typeface.createFromFile` — see *Registration,
//! the Apple way* below for why this arm needs neither a cache file nor any
//! system-wide font-manager registration call at all.
//!
//! # Why a top-level module, not `crate::apple::fonts`
//!
//! Nothing in here touches UIKit or AppKit: the whole module is
//! descriptor-from-bytes plus the first-publish latch and its caches, over
//! `objc2-core-text`/`objc2-core-foundation`, which both Apple target blocks
//! in `Cargo.toml` carry verbatim. Only the last step — handing a sized
//! `CTFont` to a control — is framework-specific, and that step lives in each
//! arm's `crate::controls::platform` (`ResolvedFont::as_ui_font` on iOS,
//! `ResolvedFont::as_ns_font` on macOS, both toll-free bridges). So the module
//! is shared rather than duplicated, and `crate::apple` stays gated on
//! `target_os = "ios"` (its own module doc's reason: gating it on
//! `target_vendor = "apple"` would link UIKit into a macOS build) — iOS call
//! sites keep reaching it as `crate::apple::fonts`, a re-export of this
//! module.
//!
//! Sharing the module is also what makes the macOS half latch its first
//! published pair exactly as the iOS half does (`docs/LIMITATIONS.md`'s
//! `native-typeface-first-publish-latch`): it is the same [`OnceLock`], not a
//! parallel implementation that could drift from it.
//!
//! # `crate::api::theme::publish_font_bytes` is the only caller of
//! [`set_glyph_bytes`]
//!
//! Same api→runtime seam as the Android half (`crate::android::fonts`'s own
//! doc): this module never names `frust-theme` directly. This crate's
//! `Cargo.toml` allows a `frust-theme` dependency only behind the
//! `frust-api` feature, and only `crate::api::theme` ever names it; the
//! plain `&'static [u8]` slices cross into this module the same way they
//! cross into the Android one.
//!
//! # Registration, the Apple way: a descriptor, not a registered font
//!
//! Android's `Typeface.createFromFile` needs an actual file path — there is
//! no create-from-bytes overload at this crate's `minSdk` floor
//! (`crate::android::fonts`'s own doc) — so that half writes the embedded
//! bytes to a content-hash-keyed cache file once. CoreText carries no such
//! restriction: `CTFontManagerCreateFontDescriptorFromData` builds a real
//! `CTFontDescriptor` straight from an in-memory `CFData`, and a descriptor
//! needs no *system-wide* registration
//! (`CTFontManagerRegisterFontDescriptors`/the deprecated
//! `CTFontManagerRegisterGraphicsFont`) to become usable —
//! `CTFont::with_font_descriptor` resolves a real, sized `CTFont` straight
//! from it, and a `CTFont` **is** a `UIFont` on iOS and an `NSFont` on macOS
//! (the same underlying object, toll-free bridged — `objc2-ui-kit`'s
//! `AsRef<UIFont> for CTFont` and `objc2-app-kit`'s `AsRef<NSFont> for
//! CTFont`, each gated on this crate's `objc2-core-text` feature on that
//! framework crate, `Cargo.toml`) — not a cast, a bridge. So this arm needs
//! no cache file, no font-manager registration call, and — unlike the modern
//! `CTFontManagerRegisterFontDescriptors` API, whose `registrationHandler`
//! is a `block2` completion block this crate would otherwise have to
//! synchronize against — no asynchrony to reason about at all: resolution is
//! synchronous data-in, `CTFontDescriptor`-out, exactly like every other
//! call this crate makes into UIKit/AppKit/CoreText.
//!
//! The descriptor (family-level; no point size baked in, since
//! `CTFontManagerCreateFontDescriptorFromData` returns one independent of
//! size — matching `CTFontDescriptor`'s own documented shape) is cached once
//! per face here, mirroring Android's cached `Typeface` object. Its caller
//! ([`crate::controls::platform::resolve_font`]) builds a freshly **sized**
//! `CTFont` from it on every apply — the same "cheap per-size construction
//! over a cached face" shape `Typeface` + `setTextSize` already has on
//! Android, forced here by `UIFont`/`NSFont`/`CTFont`'s own immutability
//! (family and point size bake into one object, unlike Android's two
//! independent setters) — see `crate::controls::platform`'s `FontState` doc
//! (iOS) and its `set_text_size`/`set_typeface` docs (macOS) for the
//! consequence that has for each arm's setter application.
//!
//! # One-time resolution, never cached on failure
//!
//! [`descriptor_for`] resolves (and thread-locally caches, in [`MONO`]/
//! [`PLEX`] — see those statics' own doc for why this arm's cache is
//! thread-local rather than the process-wide `static` Android's equivalent
//! cache uses) each face's descriptor the first time it is asked for. A
//! failed resolution is **not** cached — mirroring `crate::android::fonts`'s
//! identical rule — so a later call retries fresh instead of latching a
//! permanent degrade.
//!
//! # Degrade path
//!
//! A resolution failure (invalid font data, or
//! `CTFontManagerCreateFontDescriptorFromData` returning `None`) degrades to
//! [`Typeface::System`]'s own meaning — [`descriptor_for`] returns `None`,
//! and its caller falls back to the system font — and logs **one** warning
//! for the whole process lifetime ([`WARNED`]), mirroring
//! `crate::android::fonts`'s identical contract: never a crash, never a
//! panic across FFI (`docs/CODE_STANDARDS.md`'s no-unwind rule).
//!
//! # Font-byte redistribution note
//!
//! Same shape as the Android half: [`Typeface::GlyphMono`]/
//! [`Typeface::GlyphPlex`] are publish slots carrying whatever face a design
//! system attached through `frust_theme::NativeTypefaces` (`frust-glyph`
//! attaches Space Mono / IBM Plex Mono, SIL Open Font License 1.1 — see
//! `plugins/glyph/fonts/README.md`; another design system's licence terms are
//! its own). This module never persists a face anywhere a user or another app
//! can reach — a `CTFontDescriptor` sits in this process's own memory for
//! this process's own lifetime, no file, no `Persistent`/`Session` scope
//! registration.
use RefCell;
use ;
use ;
use ;
use crateNativeWidgetError;
use crate;
/// This process's Glyph font bytes, published once by [`crate::api::theme`]
/// — mirrors `crate::android::fonts::GlyphBytes` exactly (same seam, same
/// idempotent-publish contract). Plain `&'static [u8]` slices are `Sync`, so
/// this half of the cache stays a process-wide [`OnceLock`] like Android's —
/// only the *descriptor* cache below needs a different shape.
static BYTES: = new;
/// Publish this process's Glyph font bytes — idempotent (the first call
/// wins; a later call, with the same or different bytes, is a silent
/// no-op), mirroring `crate::android::fonts::set_glyph_bytes`'s identical
/// contract. Never called at all when the `frust-api` feature is off, or on
/// a process that never resolves a `Theme`, in which case [`descriptor_for`]
/// simply never finds published bytes and every control quietly stays on
/// [`Typeface::System`] — no warning, since nothing was ever asked to
/// resolve.
///
/// First-call-wins is why a *live* face swap does not re-register here: the
/// caller's own guard already skips an unchanged pair and re-publishes a
/// changed one, but this arm latches the first (see `crate::api::theme`'s
/// module doc — widening it is a platform-half change owing its own device
/// gate).
pub
thread_local!
/// The one warning this whole module ever logs (module doc's degrade path).
static WARNED: Once = new;
/// Resolve `typeface` to its thread-locally cached [`CTFontDescriptor`] —
/// `None` for [`Typeface::System`] (the caller's own system-font fallback) or
/// on any resolution failure (module doc's degrade path).
///
/// Returns an owned, cloned [`CFRetained`] rather than a `'static` reference
/// (`CFRetained::clone` is a cheap `CFRetain` — an ARC-style refcount bump,
/// not a copy) — the thread-local cache below has no `'static` storage to
/// borrow from, unlike Android's process-wide `OnceLock`.
pub
/// Resolve-and-cache one face — see [`descriptor_for`].
/// `CTFontManagerCreateFontDescriptorFromData` — the whole registration this
/// arm needs (module doc's *Registration, the Apple way*): no cache file, no
/// font-manager call, no `block2` completion handler.
///
/// # Errors
/// [`NativeWidgetError::Platform`] when the bytes are not a font CoreText
/// recognizes (or contain a font collection with none — the same "only the
/// first font" caveat the single-descriptor CoreText call documents).
/// See [`WARNED`].