frust-engine 0.5.0

Sparse-strip GPU render pipeline for Frust: compiles a scene display list into strips and records the draw passes on wgpu.
Documentation
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
//! Glyph-run lowering: what a run of text costs a frame, and what it refuses.
//!
//! Every case drives the public seam — a `frust_scene::Scene` recorded through
//! `SceneBuilder`, compiled by `SceneCompiler` — and reads the result back
//! through the frame's draws, its encoded-paint table and its own glyph
//! counters, so the assertions pin the lowering decisions a GPU pass would act
//! on rather than the backend's internals.
//!
//! No GPU, device or surface is involved, and no shaping engine either: the
//! glyph ids below are named directly against the bundled test faces in
//! `testing/fonts/`, which is what keeps these cases independent of parley and
//! of whatever fonts the host has installed. The *pixel* comparison against
//! the CPU reference renderer is a GPU-bound concern and lives with the
//! phase's golden corpus, not here — including the `unit-glyph-run` and
//! `adv-10k-glyphs` cases, both of which need the `frust-testing` corpus this
//! crate does not depend on. What is host-testable about text — one draw per
//! inked glyph, one encoded paint per run, dense painter-order depths, and a
//! refused frame for numbers that would not converge — is pinned below.
//! Colour glyphs have a file of their own, `text_color_hint.rs`; what is
//! pinned here is only that one does not disturb the run around it.

use std::sync::Arc;
use std::time::{Duration, Instant};

use frust_engine::cache::images::ImageResidency;
use frust_engine::compile::CompiledFrame;
use frust_engine::{AtlasBudget, EngineError, SceneCompiler};
use frust_scene::{FontHandle, Glyph, GlyphRun, Scene, SceneBuilder};
use kurbo::{Affine, Rect};
use peniko::color::palette::css::{BLUE, RED};
use peniko::color::{ColorSpaceTag, DynamicColor, HueDirection};
use peniko::{
    Blob, Brush, Color, ColorStop, ColorStops, FontData, Gradient, GradientKind,
    LinearGradientPosition,
};
use vello_common::paint::Paint;

/// Viewport every case compiles against. Deliberately not square, so an axis
/// swapped somewhere in the lowering cannot pass by symmetry.
const VIEWPORT: (u16, u16) = (128, 64);

/// Noto Sans, subsetted to Latin plus combining marks — the same bundled face
/// `frust-testing`'s deterministic text goldens shape against.
const LATIN_FONT: &[u8] = include_bytes!("../../../testing/fonts/NotoSans-Subset.ttf");

/// Noto Emoji, subsetted to one COLRv1 colour glyph.
const EMOJI_FONT: &[u8] = include_bytes!("../../../testing/fonts/NotoEmoji-COLRv1-Subset.ttf");

/// The glyph ids `HELLO_GLYPHS` is spelled from, read off [`LATIN_FONT`]'s own
/// character map: `H`, `e`, `l`, `o`. Every one of them carries an outline.
const H: u32 = 5;
const E: u32 = 6;
const L: u32 = 7;
const O: u32 = 8;

/// `Hello` — five inked glyphs, two of them the same.
const HELLO_GLYPHS: [u32; 5] = [H, E, L, L, O];

/// [`LATIN_FONT`]'s space (U+0020), the one mapped glyph in that face with no
/// outline at all. The negative control for "one draw per *inked* glyph".
const SPACE: u32 = 3;

/// [`EMOJI_FONT`]'s U+1F600, a COLRv1 base glyph with no `glyf` outline of its
/// own.
const EMOJI: u32 = 4;

/// Font size every case draws at unless it is varying it.
const FONT_SIZE: f32 = 24.0;

fn font(bytes: &'static [u8]) -> FontHandle {
    FontHandle::new(FontData::new(Blob::new(Arc::new(bytes)), 0))
}

/// A run of `ids` laid out on one baseline at `advance` pixels apart,
/// positioned by `transform`.
fn run(handle: FontHandle, ids: &[u32], advance: f32, brush: Brush, transform: Affine) -> GlyphRun {
    GlyphRun {
        font: handle,
        font_size: FONT_SIZE,
        brush,
        transform,
        glyphs: ids
            .iter()
            .enumerate()
            .map(|(index, id)| Glyph {
                id: *id,
                x: index as f32 * advance,
                y: 0.0,
            })
            .collect(),
    }
}

/// `Hello` in `brush`, sitting inside the viewport on its baseline.
fn hello(brush: Brush) -> GlyphRun {
    run(
        font(LATIN_FONT),
        &HELLO_GLYPHS,
        18.0,
        brush,
        Affine::translate((8.0, 44.0)),
    )
}

/// A scene built by `record`, ready to compile.
fn scene_of(record: impl FnOnce(&mut SceneBuilder<'_>)) -> Scene {
    let mut scene = Scene::new();
    let mut builder = SceneBuilder::new(&mut scene);
    record(&mut builder);
    scene
}

/// A compiler drawing every glyph as outline strips.
///
/// The glyph atlas is off on purpose: what this file pins is the *outline*
/// lowering — one draw per inked glyph, the run's brush inline, dense
/// painter-order depths — which is a live path in its own right (an animating
/// size, a size past the cache ceiling, a transform `glifo` will not cache, a
/// full atlas, and `FRUST_ENGINE_NO_ATLAS` all take it) and the fallback every
/// atlas refusal lands on. Turning the atlas on would make every one of these
/// runs an image draw sampling a slot, which is a different contract and is
/// pinned as one in `atlas_churn.rs`.
fn compiler() -> SceneCompiler {
    let mut compiler = SceneCompiler::new(VIEWPORT.0, VIEWPORT.1);
    compiler.set_image_residency(ImageResidency::disabled(AtlasBudget::MOBILE));
    compiler
}

fn compile(scene: &Scene) -> CompiledFrame {
    compiler()
        .compile(scene, Affine::IDENTITY, VIEWPORT)
        .expect("an in-range scene compiles")
}

/// The frame a single glyph run produces, with nothing else recorded.
fn compile_run(glyph_run: GlyphRun) -> CompiledFrame {
    compile(&scene_of(|builder| builder.draw_glyph_run(glyph_run)))
}

/// A three-stop linear gradient spanning the run's own width.
fn gradient_brush() -> Brush {
    Brush::Gradient(Gradient {
        kind: GradientKind::Linear(LinearGradientPosition {
            start: kurbo::Point::new(0.0, 0.0),
            end: kurbo::Point::new(96.0, 0.0),
        }),
        stops: ColorStops(
            vec![
                ColorStop {
                    offset: 0.0,
                    color: DynamicColor::from_alpha_color(RED),
                },
                ColorStop {
                    offset: 1.0,
                    color: DynamicColor::from_alpha_color(BLUE),
                },
            ]
            .into(),
        ),
        interpolation_cs: ColorSpaceTag::Srgb,
        hue_direction: HueDirection::Shorter,
        ..Default::default()
    })
}

// ---------------------------------------------------------------------
// One draw per inked glyph
// ---------------------------------------------------------------------

#[test]
fn every_inked_glyph_of_a_run_becomes_one_draw() {
    let frame = compile_run(hello(Brush::Solid(RED)));

    assert_eq!(
        frame.glyph_draws,
        HELLO_GLYPHS.len() as u32,
        "each of the five outlines in `Hello` paints once"
    );
    assert_eq!(frame.draws().len(), HELLO_GLYPHS.len());
    assert_eq!(frame.skipped_glyphs, 0, "an outline face refuses nothing");
    assert!(
        !frame.strip_buf().is_empty(),
        "a drawn glyph carries real coverage"
    );
}

#[test]
fn a_glyph_with_no_outline_records_no_draw() {
    let frame = compile_run(run(
        font(LATIN_FONT),
        &[SPACE, SPACE],
        18.0,
        Brush::Solid(RED),
        Affine::translate((8.0, 44.0)),
    ));

    assert_eq!(frame.glyph_draws, 0, "a space has no ink to paint");
    assert!(frame.draws().is_empty());
    assert_eq!(
        frame.skipped_glyphs, 0,
        "an empty outline is drawn and found empty, not refused"
    );
    assert!(frame.strip_buf().is_empty());
}

#[test]
fn an_empty_run_records_nothing() {
    let frame = compile_run(run(
        font(LATIN_FONT),
        &[],
        0.0,
        Brush::Solid(RED),
        Affine::IDENTITY,
    ));

    assert!(frame.draws().is_empty());
    assert_eq!(frame.glyph_draws, 0);
    assert!(frame.encoded_paints.is_empty(), "and encodes no paint");
}

#[test]
fn a_run_placed_outside_the_viewport_records_nothing() {
    let frame = compile_run(run(
        font(LATIN_FONT),
        &HELLO_GLYPHS,
        18.0,
        Brush::Solid(RED),
        Affine::translate((4000.0, 4000.0)),
    ));

    assert!(frame.draws().is_empty(), "every glyph culls away");
    assert_eq!(frame.glyph_draws, 0);
}

#[test]
fn a_run_under_a_clip_that_admits_nothing_records_nothing() {
    let frame = compile(&scene_of(|builder| {
        builder.push_clip(Rect::ZERO);
        builder.draw_glyph_run(hello(Brush::Solid(RED)));
        builder.pop_clip();
    }));

    assert!(frame.draws().is_empty());
    assert_eq!(frame.glyph_draws, 0);
    assert!(
        frame.encoded_paints.is_empty(),
        "a run that cannot draw leaves no orphan paint entry behind"
    );
}

#[test]
fn a_clip_trims_a_run_to_its_own_rectangle() {
    let unclipped = compile_run(hello(Brush::Solid(RED)));
    let clipped = compile(&scene_of(|builder| {
        // Whole pixels and axis-aligned, so the clip scissors rather than
        // masking: the trimming below is the scissor's, applied to the glyph
        // coverage after it was generated.
        builder.push_clip(Rect::new(0.0, 0.0, 40.0, 64.0));
        builder.draw_glyph_run(hello(Brush::Solid(RED)));
        builder.pop_clip();
    }));

    assert_eq!(clipped.scissor_clips, 1, "the clip lowered to a scissor");
    assert_eq!(clipped.mask_clips, 0);
    assert!(
        clipped.glyph_draws < unclipped.glyph_draws,
        "the clip cuts the run's trailing glyphs away entirely \
         ({} of {} survive)",
        clipped.glyph_draws,
        unclipped.glyph_draws
    );
    assert!(clipped.glyph_draws > 0, "and keeps its leading ones");
}

// ---------------------------------------------------------------------
// One paint per run
// ---------------------------------------------------------------------

#[test]
fn a_solid_run_paints_every_glyph_with_one_inline_colour() {
    let frame = compile_run(hello(Brush::Solid(RED)));

    assert!(
        frame.encoded_paints.is_empty(),
        "a solid colour travels inside the paint and costs no side-table entry"
    );
    assert!(frame.lut_requests.is_empty());
    for draw in frame.draws() {
        assert!(
            matches!(draw.paint, Paint::Solid(_)),
            "every glyph of the run carries the run's own colour"
        );
    }
}

#[test]
fn a_gradient_brushed_run_encodes_one_entry_and_one_ramp_for_the_whole_run() {
    let frame = compile_run(hello(gradient_brush()));

    assert_eq!(
        frame.encoded_paints.len(),
        1,
        "the run's brush is encoded once, not once per glyph"
    );
    assert_eq!(
        frame.lut_requests.len(),
        1,
        "and asks for exactly one colour ramp"
    );
    assert_eq!(frame.glyph_draws, HELLO_GLYPHS.len() as u32);

    let indices: Vec<usize> = frame
        .draws()
        .iter()
        .map(|draw| match &draw.paint {
            Paint::Indexed(indexed) => indexed.index(),
            Paint::Solid(_) => panic!("a gradient-brushed glyph paints from the side table"),
        })
        .collect();
    assert!(
        indices.iter().all(|index| *index == 0),
        "every glyph references the same encoded entry: {indices:?}"
    );
}

// ---------------------------------------------------------------------
// Painter order
// ---------------------------------------------------------------------

#[test]
fn glyph_depths_are_dense_and_run_in_painter_order() {
    let frame = compile(&scene_of(|builder| {
        builder.fill_rect(Rect::new(0.0, 0.0, 128.0, 64.0), Brush::Solid(BLUE));
        builder.draw_glyph_run(hello(Brush::Solid(RED)));
    }));

    let depths: Vec<u32> = frame.draws().iter().map(|draw| draw.depth).collect();
    let expected: Vec<u32> = (0..depths.len() as u32).collect();
    assert_eq!(
        depths, expected,
        "the background takes depth 0 and each glyph the next, with no gap \
         left by a glyph that drew nothing"
    );
}

// ---------------------------------------------------------------------
// Refusals: numbers, and glyphs the engine cannot paint
// ---------------------------------------------------------------------

#[test]
fn a_non_finite_font_size_refuses_the_frame() {
    let mut glyph_run = hello(Brush::Solid(RED));
    glyph_run.font_size = f32::NAN;
    let scene = scene_of(|builder| builder.draw_glyph_run(glyph_run));

    assert!(matches!(
        compiler().compile(&scene, Affine::IDENTITY, VIEWPORT),
        Err(EngineError::InvalidGeometry)
    ));
}

#[test]
fn a_non_finite_glyph_position_refuses_the_frame() {
    let mut glyph_run = hello(Brush::Solid(RED));
    if let Some(glyph) = glyph_run.glyphs.get_mut(2) {
        glyph.x = f32::INFINITY;
    }
    let scene = scene_of(|builder| builder.draw_glyph_run(glyph_run));

    assert!(matches!(
        compiler().compile(&scene, Affine::IDENTITY, VIEWPORT),
        Err(EngineError::InvalidGeometry)
    ));
}

#[test]
fn a_non_finite_run_transform_refuses_the_frame() {
    let glyph_run = run(
        font(LATIN_FONT),
        &HELLO_GLYPHS,
        18.0,
        Brush::Solid(RED),
        Affine::scale(f64::NAN),
    );
    let scene = scene_of(|builder| builder.draw_glyph_run(glyph_run));

    assert!(matches!(
        compiler().compile(&scene, Affine::IDENTITY, VIEWPORT),
        Err(EngineError::InvalidTransform)
    ));
}

#[test]
fn a_colour_glyph_paints_its_own_layers_rather_than_the_runs_brush() {
    // The tripwire only; `text_color_hint.rs` pins what those layers are, what
    // they paint with, and what a colour glyph the engine cannot express does
    // instead.
    let frame = compile_run(run(
        font(EMOJI_FONT),
        &[EMOJI],
        32.0,
        Brush::Solid(RED),
        Affine::translate((8.0, 44.0)),
    ));

    assert_eq!(frame.glyph_draws, 1, "a COLR glyph is drawn, not refused");
    assert_eq!(frame.skipped_glyphs, 0);
    assert!(
        frame.draws().len() > 1,
        "and costs one draw per colour layer"
    );
    assert!(!frame.strip_buf().is_empty());
}

#[test]
fn a_colour_glyph_leaves_the_frames_own_clip_state_untouched() {
    // The COLR lowering brackets its layers in clip pushes and pops of its
    // own. If those reached the compiler's clip stack, the outline glyphs
    // recorded after the emoji would be drawn under a clip nothing closed.
    let outlines_only = compile_run(hello(Brush::Solid(RED)));
    let after_emoji = compile(&scene_of(|builder| {
        builder.draw_glyph_run(run(
            font(EMOJI_FONT),
            &[EMOJI],
            32.0,
            Brush::Solid(RED),
            Affine::translate((96.0, 44.0)),
        ));
        builder.draw_glyph_run(hello(Brush::Solid(RED)));
    }));

    assert_eq!(
        after_emoji.glyph_draws,
        outlines_only.glyph_draws + 1,
        "every outline glyph after a colour glyph still paints, and the \
         colour glyph itself counts once"
    );
    assert_eq!(
        after_emoji.scissor_clips, 0,
        "and the colour glyph pushed no clip of the compiler's"
    );
    assert_eq!(after_emoji.mask_clips, 0);
}

// ---------------------------------------------------------------------
// The font gate
// ---------------------------------------------------------------------

/// A run of two glyphs against `bytes` read as a font face at `index`.
fn run_against(bytes: Vec<u8>, index: u32) -> GlyphRun {
    GlyphRun {
        font: FontHandle::new(FontData::new(Blob::from(bytes), index)),
        font_size: FONT_SIZE,
        brush: Brush::Solid(RED),
        transform: Affine::translate((8.0, 44.0)),
        glyphs: vec![
            Glyph {
                id: H,
                x: 0.0,
                y: 0.0,
            },
            Glyph {
                id: E,
                x: 18.0,
                y: 0.0,
            },
        ],
    }
}

/// A blob that is longer than a table directory and carries a plausible file
/// tag, but names tables that are not there. The parser this gate stands in
/// front of accepts blobs on much weaker evidence than "is a font", so the
/// gate has to reach the `head` table to be worth anything.
fn plausible_but_empty_face() -> Vec<u8> {
    let mut bytes = vec![0x00, 0x01, 0x00, 0x00];
    // numTables = 0, then searchRange/entrySelector/rangeShift.
    bytes.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0]);
    bytes
}

#[test]
fn a_font_blob_that_is_not_a_face_skips_its_run_instead_of_panicking() {
    for (label, bytes, index) in [
        ("an unloaded font resource", Vec::new(), 0),
        ("a truncated read", vec![1_u8, 2, 3, 4], 0),
        ("a face with no head table", plausible_but_empty_face(), 0),
        (
            "a collection index no collection names",
            LATIN_FONT.to_vec(),
            7,
        ),
    ] {
        let glyph_run = run_against(bytes, index);
        let glyphs = glyph_run.glyphs.len() as u32;
        let frame = compile_run(glyph_run);

        assert_eq!(frame.glyph_draws, 0, "{label} paints nothing");
        assert_eq!(
            frame.skipped_glyphs, glyphs,
            "{label} counts its whole run as skipped"
        );
        assert!(
            frame.encoded_paints.is_empty(),
            "{label} is refused before its brush is encoded"
        );
        assert!(frame.draws().is_empty(), "{label} records no draw");
    }
}

#[test]
fn the_font_gate_still_admits_the_bundled_faces() {
    // The negative controls above are only meaningful next to a positive one:
    // a gate that refused everything would pass all of them.
    assert!(compile_run(run_against(LATIN_FONT.to_vec(), 0)).glyph_draws > 0);
}

/// Wraps `font` — a standalone single-face sfnt file — as the sole member of
/// a spec-correct synthetic `ttcf` collection: header (major version 1, one
/// font) followed by `font`'s own table directory and table data, with every
/// record's offset shifted by the header's length so it still resolves at
/// the same table bytes now that they sit further into the blob.
///
/// The shift is what makes this a real collection rather than a relabelled
/// single font: a TTC's table-record offsets are absolute to the whole file
/// (that is what lets fonts in a real collection share table data), so
/// copying `font`'s directory unshifted behind a header would point every
/// record at the wrong bytes.
fn wrap_as_collection(font: &[u8]) -> Vec<u8> {
    const HEADER_LEN: u32 = 16;
    let num_tables = u16::from_be_bytes([font[4], font[5]]) as usize;
    let data_start = 12 + num_tables * 16;

    let mut out = Vec::with_capacity(HEADER_LEN as usize + font.len());
    out.extend_from_slice(b"ttcf");
    out.extend_from_slice(&1u16.to_be_bytes()); // majorVersion
    out.extend_from_slice(&0u16.to_be_bytes()); // minorVersion
    out.extend_from_slice(&1u32.to_be_bytes()); // numFonts
    out.extend_from_slice(&HEADER_LEN.to_be_bytes()); // offsetTable[0]

    // The wrapped font's own directory header: sfntVersion, numTables,
    // searchRange, entrySelector, rangeShift — none of these are offsets, so
    // none of them need shifting.
    out.extend_from_slice(&font[0..12]);
    for index in 0..num_tables {
        let record = 12 + index * 16;
        out.extend_from_slice(&font[record..record + 8]); // tag + checksum
        let offset = u32::from_be_bytes(font[record + 8..record + 12].try_into().unwrap());
        out.extend_from_slice(&(offset + HEADER_LEN).to_be_bytes());
        out.extend_from_slice(&font[record + 12..record + 16]); // length
    }
    // The table data itself, copied as one block starting right where the
    // shifted records now expect it.
    out.extend_from_slice(&font[data_start..]);

    out
}

#[test]
fn a_spec_correct_ttcf_collection_is_admitted() {
    let frame = compile_run(run_against(wrap_as_collection(LATIN_FONT), 0));
    assert!(
        frame.glyph_draws > 0,
        "a collection wrapping a real face at the spec's own offsets is not refused"
    );
}

#[test]
fn a_malformed_ttcf_collection_fails_closed_instead_of_admitting_a_bypass() {
    let base = wrap_as_collection(LATIN_FONT);

    let mut undefined_major_version = base.clone();
    // majorVersion lives at byte 4; only 1 and 2 are defined.
    undefined_major_version[4..6].copy_from_slice(&3u16.to_be_bytes());

    let mut bogus_inner_sfnt_tag = base.clone();
    // The inner table directory starts right after the 16-byte header; its
    // first four bytes are the sfnt tag the single-font branch would also
    // check at offset 0.
    bogus_inner_sfnt_tag[16..20].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);

    let mut directory_offset_past_the_blob = base.clone();
    let past_the_blob = base.len() as u32 + 4096;
    // offsetTable[0] lives at byte 12 (TTC_OFFSETS).
    directory_offset_past_the_blob[12..16].copy_from_slice(&past_the_blob.to_be_bytes());

    let mut inflated_num_fonts = base.clone();
    // numFonts lives at byte 8 (TTC_NUM_FONTS). Inflating it past what the
    // blob can hold for a *full* offsets array (TTC_OFFSETS + numFonts * 4)
    // must fail closed even though index 0's own entry — offsetTable[0],
    // still spec-correct — resolves to a real face: read-fonts 0.41.0's
    // `TTCHeader::table_directory_offsets()` silently yields an *empty*
    // array on this overrun rather than erring, so `CollectionRef::get`
    // answers `InvalidCollectionIndex` and `glifo`'s
    // `FontRef::from_index(..).unwrap()` aborts the process (panic=abort).
    // The gate has to check the array's own fit, not just the one entry an
    // accepted index would read.
    let inflated: u32 = (base.len() as u32 - 12) / 4 + 1000;
    inflated_num_fonts[8..12].copy_from_slice(&inflated.to_be_bytes());

    for (label, bytes, index) in [
        (
            "a TTC major version outside the defined 1/2",
            undefined_major_version,
            0,
        ),
        (
            "an inner table directory with no known sfnt tag",
            bogus_inner_sfnt_tag,
            0,
        ),
        (
            "a table-directory offset past the end of the blob",
            directory_offset_past_the_blob,
            0,
        ),
        (
            "an index past the collection's own numFonts bound, read from \
             the spec's offset rather than the neighbouring offset table",
            base.clone(),
            1,
        ),
        (
            "an inflated numFonts whose implied offsets array does not fit \
             the blob, even though the requested index's own entry still \
             resolves to a real face",
            inflated_num_fonts,
            0,
        ),
    ] {
        let glyph_run = run_against(bytes, index);
        let glyphs = glyph_run.glyphs.len() as u32;
        let frame = compile_run(glyph_run);

        assert_eq!(frame.glyph_draws, 0, "{label} paints nothing");
        assert_eq!(
            frame.skipped_glyphs, glyphs,
            "{label} counts its whole run as skipped"
        );
        assert!(
            frame.encoded_paints.is_empty(),
            "{label} is refused before its brush is encoded"
        );
        assert!(frame.draws().is_empty(), "{label} records no draw");
    }
}

// ---------------------------------------------------------------------
// The retained caches
// ---------------------------------------------------------------------

#[test]
fn recompiling_the_same_run_against_a_warm_outline_cache_is_byte_identical() {
    let scene = scene_of(|builder| builder.draw_glyph_run(hello(Brush::Solid(RED))));
    let mut compiler = compiler();

    let cold = compiler
        .compile(&scene, Affine::IDENTITY, VIEWPORT)
        .expect("the first frame compiles");
    let warm = compiler
        .compile(&scene, Affine::IDENTITY, VIEWPORT)
        .expect("the second frame compiles");

    assert_eq!(
        cold.strip_buf(),
        warm.strip_buf(),
        "a cached outline rasterizes to the strips its cold fetch did"
    );
    assert_eq!(cold.alphas(), warm.alphas());
    assert_eq!(cold.glyph_draws, warm.glyph_draws);
}

#[test]
fn a_larger_font_size_is_a_distinct_cache_entry_and_paints_more_coverage() {
    let mut compiler = compiler();
    let mut small = hello(Brush::Solid(RED));
    small.font_size = 12.0;
    let mut large = hello(Brush::Solid(RED));
    large.font_size = 36.0;

    let small_frame = compiler
        .compile(
            &scene_of(|builder| builder.draw_glyph_run(small)),
            Affine::IDENTITY,
            VIEWPORT,
        )
        .expect("the small run compiles");
    let small_alphas = small_frame.alphas().len();

    let large_frame = compiler
        .compile(
            &scene_of(|builder| builder.draw_glyph_run(large)),
            Affine::IDENTITY,
            VIEWPORT,
        )
        .expect("the large run compiles");

    assert_eq!(small_frame.glyph_draws, large_frame.glyph_draws);
    assert!(
        large_frame.alphas().len() > small_alphas,
        "the larger run covers more pixels ({} vs {}) — the size is part of \
         the outline cache key, not something a warm entry ignores",
        large_frame.alphas().len(),
        small_alphas
    );
}

// ---------------------------------------------------------------------
// The adversarial run
// ---------------------------------------------------------------------

/// Densely packed glyphs, at the scale a long unbroken token reaches.
const MANY: usize = 10_000;

/// A ceiling on the whole compile, not a performance figure.
///
/// The point of the case is that a run's work stays bounded by its glyph count
/// — no unbounded flattening, no per-glyph font-table re-parse, no cache that
/// grows without ageing — so the bound sits far above any plausible healthy
/// time (this compile measures around 0.12 s in the same unoptimized test
/// profile) and is a tripwire for work that is not bounded at all. A real
/// timing lives in the crate's benches, under a release profile this test
/// target does not have.
const MANY_BUDGET: Duration = Duration::from_secs(10);

#[test]
fn ten_thousand_glyphs_compile_without_panicking_and_within_a_bound() {
    let glyphs: Vec<Glyph> = (0..MANY)
        .map(|index| Glyph {
            id: O,
            x: (index % 120) as f32,
            y: (index / 120 % 60) as f32,
        })
        .collect();
    let glyph_run = GlyphRun {
        font: font(LATIN_FONT),
        font_size: 14.0,
        brush: Brush::Solid(Color::from_rgb8(0x20, 0x20, 0x20)),
        transform: Affine::IDENTITY,
        glyphs,
    };

    let scene = scene_of(|builder| builder.draw_glyph_run(glyph_run));
    let started = Instant::now();
    let frame = compile(&scene);
    let elapsed = started.elapsed();

    assert_eq!(
        frame.glyph_draws, MANY as u32,
        "every glyph of the run paints"
    );
    assert_eq!(frame.skipped_glyphs, 0);
    assert_eq!(
        frame.encoded_paints.len(),
        0,
        "and all ten thousand share the run's one inline colour"
    );
    assert!(
        elapsed < MANY_BUDGET,
        "compiling {MANY} glyphs took {elapsed:?}, past the {MANY_BUDGET:?} bound"
    );
}