1use alloc::string::String;
2use alloc::vec::Vec;
3use core::cell::RefCell;
4
5pub trait Platform {
7 fn diagnostics(&self) -> &dyn DiagnosticSink;
9
10 fn limits(&self) -> HostLimits {
12 HostLimits::default()
13 }
14
15 fn clock(&self) -> HostClock {
17 HostClock::default()
18 }
19
20 fn linebreak_start(&self, _request: LinebreakRequest<'_>) {}
22
23 fn linebreak_next(&self) -> Option<i32> {
25 None
26 }
27
28 fn host_box(&self, _request: HostBoxRequest) -> Option<HostBox> {
30 None
31 }
32}
33
34impl<T> Platform for &T
35where
36 T: Platform,
37{
38 fn diagnostics(&self) -> &dyn DiagnosticSink {
39 (*self).diagnostics()
40 }
41
42 fn limits(&self) -> HostLimits {
43 (*self).limits()
44 }
45
46 fn clock(&self) -> HostClock {
47 (*self).clock()
48 }
49
50 fn linebreak_start(&self, request: LinebreakRequest<'_>) {
51 (*self).linebreak_start(request);
52 }
53
54 fn linebreak_next(&self) -> Option<i32> {
55 (*self).linebreak_next()
56 }
57
58 fn host_box(&self, request: HostBoxRequest) -> Option<HostBox> {
59 (*self).host_box(request)
60 }
61}
62
63pub trait DiagnosticSink {
65 fn emit(&self, diagnostic: Diagnostic);
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct Diagnostic {
72 pub severity: DiagnosticSeverity,
74 pub message: String,
76}
77
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80#[non_exhaustive]
81pub enum DiagnosticSeverity {
82 Info,
84 Warning,
86 Error,
88}
89
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
92pub struct HostLimits {
93 pub max_input_bytes: usize,
95 pub max_resource_requests: usize,
97 pub max_layout_nodes: usize,
99}
100
101impl Default for HostLimits {
102 fn default() -> Self {
103 Self {
104 max_input_bytes: 1 << 20,
105 max_resource_requests: 256,
106 max_layout_nodes: 1 << 20,
107 }
108 }
109}
110
111#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
113pub struct HostClock {
114 pub seconds: i32,
116 pub micros: i32,
118}
119
120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
122pub struct LinebreakRequest<'a> {
123 pub font: i32,
125 pub locale: i32,
127 pub text: &'a [u16],
129}
130
131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
133pub enum HostBoxStyle {
134 Text,
136 Script,
138 ScriptScript,
140}
141
142#[derive(Clone, Copy, Debug, PartialEq, Eq)]
144pub struct HostBoxRequest {
145 pub token: i32,
147 pub style: HostBoxStyle,
149 pub font_size: i32,
151}
152
153#[derive(Clone, Copy, Debug, PartialEq, Eq)]
155pub struct HostBoxGlyph {
156 pub glyph: u16,
158 pub x: i32,
160 pub y: i32,
162 pub advance: i32,
164}
165
166#[derive(Clone, Debug, PartialEq, Eq)]
168pub struct HostBoxRun {
169 pub font_name: String,
171 pub font_size: i32,
173 pub glyphs: Vec<HostBoxGlyph>,
175}
176
177#[derive(Clone, Copy, Debug, PartialEq, Eq)]
179pub struct HostBoxRule {
180 pub x: i32,
182 pub y: i32,
184 pub width: i32,
186 pub height: i32,
188}
189
190#[derive(Clone, Debug, PartialEq, Eq)]
192pub struct HostBox {
193 pub width: i32,
195 pub height: i32,
197 pub depth: i32,
199 pub runs: Vec<HostBoxRun>,
201 pub rules: Vec<HostBoxRule>,
203}
204
205#[derive(Clone, Debug, PartialEq, Eq)]
207#[non_exhaustive]
208pub enum LimitError {
209 InputTooLarge {
211 actual: usize,
213 limit: usize,
215 },
216 TooManyResourceRequests {
218 actual: usize,
220 limit: usize,
222 },
223 TooManyLayoutNodes {
225 actual: usize,
227 limit: usize,
229 },
230}
231
232#[derive(Clone, Copy, Debug, Default)]
234pub struct NoopDiagnosticSink;
235
236impl DiagnosticSink for NoopDiagnosticSink {
237 fn emit(&self, _diagnostic: Diagnostic) {}
238}
239
240#[derive(Debug, Default)]
242pub struct CollectingDiagnosticSink {
243 diagnostics: RefCell<Vec<Diagnostic>>,
244}
245
246impl CollectingDiagnosticSink {
247 #[must_use]
249 pub fn new() -> Self {
250 Self::default()
251 }
252
253 #[must_use]
255 pub fn snapshot(&self) -> Vec<Diagnostic> {
256 self.diagnostics.borrow().clone()
257 }
258
259 #[must_use]
261 pub fn drain(&self) -> Vec<Diagnostic> {
262 self.diagnostics.borrow_mut().drain(..).collect()
263 }
264
265 #[must_use]
267 pub fn len(&self) -> usize {
268 self.diagnostics.borrow().len()
269 }
270
271 #[must_use]
273 pub fn is_empty(&self) -> bool {
274 self.diagnostics.borrow().is_empty()
275 }
276
277 pub fn clear(&self) {
279 self.diagnostics.borrow_mut().clear();
280 }
281}
282
283impl DiagnosticSink for CollectingDiagnosticSink {
284 fn emit(&self, diagnostic: Diagnostic) {
285 self.diagnostics.borrow_mut().push(diagnostic);
286 }
287}
288
289#[derive(Clone, Debug)]
291pub struct ConfigurablePlatform<D> {
292 diagnostics: D,
293 limits: HostLimits,
294 clock: HostClock,
295}
296
297impl<D> ConfigurablePlatform<D> {
298 #[must_use]
300 pub fn new(diagnostics: D, limits: HostLimits) -> Self {
301 Self {
302 diagnostics,
303 limits,
304 clock: HostClock::default(),
305 }
306 }
307
308 #[must_use]
310 pub fn with_diagnostics(diagnostics: D) -> Self {
311 Self {
312 diagnostics,
313 limits: HostLimits::default(),
314 clock: HostClock::default(),
315 }
316 }
317
318 #[must_use]
320 pub fn with_clock(mut self, clock: HostClock) -> Self {
321 self.clock = clock;
322 self
323 }
324
325 #[must_use]
327 pub fn diagnostic_sink(&self) -> &D {
328 &self.diagnostics
329 }
330
331 #[must_use]
333 pub fn host_limits(&self) -> HostLimits {
334 self.limits
335 }
336
337 #[must_use]
339 pub fn host_clock(&self) -> HostClock {
340 self.clock
341 }
342}
343
344impl<D> Platform for ConfigurablePlatform<D>
345where
346 D: DiagnosticSink,
347{
348 fn diagnostics(&self) -> &dyn DiagnosticSink {
349 &self.diagnostics
350 }
351
352 fn limits(&self) -> HostLimits {
353 self.limits
354 }
355
356 fn clock(&self) -> HostClock {
357 self.clock
358 }
359}
360
361#[derive(Clone, Copy, Debug, Default)]
363pub struct NoopPlatform {
364 diagnostics: NoopDiagnosticSink,
365 limits: HostLimits,
366 clock: HostClock,
367}
368
369impl NoopPlatform {
370 #[must_use]
372 pub fn with_limits(limits: HostLimits) -> Self {
373 Self {
374 diagnostics: NoopDiagnosticSink,
375 limits,
376 clock: HostClock::default(),
377 }
378 }
379
380 #[must_use]
382 pub fn with_clock(clock: HostClock) -> Self {
383 Self {
384 diagnostics: NoopDiagnosticSink,
385 limits: HostLimits::default(),
386 clock,
387 }
388 }
389}
390
391impl Platform for NoopPlatform {
392 fn diagnostics(&self) -> &dyn DiagnosticSink {
393 &self.diagnostics
394 }
395
396 fn limits(&self) -> HostLimits {
397 self.limits
398 }
399
400 fn clock(&self) -> HostClock {
401 self.clock
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408
409 #[test]
410 fn collecting_diagnostic_sink_records_snapshots_and_drains() {
411 let sink = CollectingDiagnosticSink::new();
412
413 sink.emit(Diagnostic {
414 severity: DiagnosticSeverity::Warning,
415 message: "missing glyph".to_string(),
416 });
417
418 assert_eq!(sink.len(), 1);
419 assert_eq!(sink.snapshot()[0].message, "missing glyph");
420 assert_eq!(sink.drain()[0].severity, DiagnosticSeverity::Warning);
421 assert!(sink.is_empty());
422 }
423
424}