Skip to main content

pfx_text/
lib.rs

1use std::collections::{BTreeMap, BTreeSet, HashMap};
2use std::hash::{Hash, Hasher};
3
4use cosmic_text::{
5    Align, Attrs, Buffer, CacheKeyFlags, Cursor, Family, FeatureTag, FontFeatures, FontSystem,
6    Metrics, Shaping, SubpixelBin, Wrap, fontdb,
7};
8use swash::scale::{Render, ScaleContext, Source, StrikeWith, image::Content};
9use swash::zeno::{Angle, Command, PathData, Transform, Vector};
10
11mod atlas;
12#[cfg(test)]
13mod atlas_tests;
14mod fallback;
15#[cfg(test)]
16mod fallback_tests;
17mod field;
18#[cfg(test)]
19mod field_reference;
20#[cfg(any(test, feature = "fixture"))]
21pub mod fixture;
22mod number;
23mod prefill;
24mod print;
25#[cfg(test)]
26mod quality;
27#[cfg(feature = "subset")]
28pub mod subset;
29#[cfg(test)]
30mod warm_tests;
31
32pub use atlas::{
33    AtlasChanges, AtlasStats, CellUpload, DEFAULT_ATLAS_BUDGET, DEFAULT_ATLAS_PAGE, PageInfo,
34    PageKind,
35};
36pub use fallback::{FaceMetrics, Fallback, Pick, matched_scale};
37pub use number::{Figures, Ligatures, NumberForms};
38pub use prefill::Prefill;
39pub use print::{Caret, Icon, PrintBlock, PrintImage, Printed, Selection};
40
41use atlas::{CellRef, SharedAtlas};
42
43pub const ATLAS_PADDING: u32 = 8;
44pub const ATLAS_CELL_ALIGN: u32 = 8;
45pub const MSDF_FIELD: f32 = 64.0;
46pub const FINE_MSDF_FIELD: f32 = 128.0;
47pub const FINE_MSDF_FROM: f32 = 120.0;
48pub const MSDF_SPREAD: f32 = 8.0;
49
50pub const MSDF_WGSL: &str = r#"
51fn msdf_median(rgb: vec3<f32>) -> f32 {
52    return max(min(rgb.r, rgb.g), min(max(rgb.r, rgb.g), rgb.b));
53}
54fn msdf_coverage(rgb: vec3<f32>, uv: vec2<f32>, texture_size: vec2<f32>) -> f32 {
55    let signed_distance = msdf_median(rgb) - 0.5;
56    let screen_range = 4.0 * 0.5 * dot(vec2<f32>(1.0) / fwidth(uv), vec2<f32>(1.0) / texture_size);
57    return clamp(0.5 + signed_distance * screen_range, 0.0, 1.0);
58}
59fn sample_msdf(atlas: texture_2d<f32>, atlas_sampler: sampler, uv: vec2<f32>) -> f32 {
60    let rgb = textureSample(atlas, atlas_sampler, uv).rgb;
61    return msdf_coverage(rgb, uv, vec2<f32>(textureDimensions(atlas)));
62}
63"#;
64
65pub fn msdf_coverage(rgb: [f32; 3], screen_range: f32) -> f32 {
66    let median = (rgb[0].min(rgb[1])).max(rgb[0].max(rgb[1]).min(rgb[2]));
67    (0.5 + (median - 0.5) * screen_range).clamp(0.0, 1.0)
68}
69
70#[derive(Clone, Debug)]
71pub enum Baseline {
72    Straight {
73        origin: [f32; 2],
74        direction: [f32; 2],
75    },
76    Arc {
77        center: [f32; 2],
78        radius: f32,
79        start_angle: f32,
80        clockwise: bool,
81    },
82    Polyline(Vec<[f32; 2]>),
83}
84
85impl Baseline {
86    pub fn sample(&self, distance: f32, normal_offset: f32) -> Option<([f32; 2], f32)> {
87        let (point, tangent) = match self {
88            Self::Straight { origin, direction } => {
89                let len = direction[0].hypot(direction[1]);
90                if len <= 0.0 {
91                    return None;
92                }
93                let t = [direction[0] / len, direction[1] / len];
94                (
95                    [origin[0] + distance * t[0], origin[1] + distance * t[1]],
96                    t,
97                )
98            }
99            Self::Arc {
100                center,
101                radius,
102                start_angle,
103                clockwise,
104            } => {
105                if *radius <= 0.0 {
106                    return None;
107                }
108                let sign = if *clockwise { -1.0 } else { 1.0 };
109                let a = start_angle + sign * distance / radius;
110                (
111                    [center[0] + radius * a.cos(), center[1] + radius * a.sin()],
112                    [-sign * a.sin(), sign * a.cos()],
113                )
114            }
115            Self::Polyline(points) => {
116                let segments: Vec<_> = points
117                    .windows(2)
118                    .filter_map(|pair| {
119                        let dx = pair[1][0] - pair[0][0];
120                        let dy = pair[1][1] - pair[0][1];
121                        let len = dx.hypot(dy);
122                        (len > 0.0).then_some((pair[0], [dx / len, dy / len], len))
123                    })
124                    .collect();
125                let &(mut origin, mut tangent, _) = segments.first()?;
126                let mut remain = distance;
127                for &(start, dir, len) in &segments {
128                    origin = start;
129                    tangent = dir;
130                    if remain <= len {
131                        break;
132                    }
133                    remain -= len;
134                }
135                (
136                    [
137                        origin[0] + remain * tangent[0],
138                        origin[1] + remain * tangent[1],
139                    ],
140                    tangent,
141                )
142            }
143        };
144        Some((
145            [
146                point[0] - tangent[1] * normal_offset,
147                point[1] + tangent[0] * normal_offset,
148            ],
149            tangent[1].atan2(tangent[0]),
150        ))
151    }
152}
153
154#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
155pub enum Representation {
156    Coverage,
157    Msdf,
158}
159
160#[derive(Clone, Debug)]
161pub struct GlyphInstance {
162    pub glyph_id: u16,
163    pub cluster: std::ops::Range<usize>,
164    pub position: [f32; 2],
165    pub rotation: f32,
166    pub scale: f32,
167    pub local_rect: [f32; 4],
168    pub atlas_rect: [u32; 4],
169    pub uv: [f32; 4],
170}
171
172#[derive(Clone, Debug, PartialEq, Eq)]
173pub struct MipLevel {
174    pub width: u32,
175    pub height: u32,
176    pub bytes: Vec<u8>,
177}
178
179#[derive(Clone, Debug)]
180pub struct Atlas {
181    pub channels: u32,
182    pub levels: Vec<MipLevel>,
183}
184
185#[derive(Clone, Debug)]
186pub struct Paragraph {
187    pub glyphs: Vec<GlyphInstance>,
188    pub atlas: Atlas,
189    pub width: f32,
190    pub height: f32,
191}
192
193#[derive(Clone, Copy, Debug)]
194pub struct Style<'a> {
195    pub family: &'a str,
196    pub size: f32,
197    pub line_height: f32,
198    pub wrap_width: Option<f32>,
199    pub pixels_per_unit: f32,
200    pub representation: Representation,
201}
202
203#[derive(Clone, Debug, PartialEq)]
204pub struct Face {
205    pub family: String,
206    pub size: f32,
207    pub line: f32,
208    pub weight: u16,
209    pub italic: bool,
210    pub spacing: f32,
211}
212
213#[derive(Clone, Debug, PartialEq)]
214pub struct Span {
215    pub text: String,
216    pub face: Face,
217    pub color: [f32; 4],
218}
219
220#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
221pub enum Anchor {
222    Start,
223    Center,
224    End,
225}
226
227pub struct Block {
228    buffer: Buffer,
229    scale: f32,
230    pub width: f32,
231    pub height: f32,
232    pub baseline: f32,
233    stamp: u64,
234    bytes: u64,
235    spans: Vec<usize>,
236    lines: Vec<f32>,
237    shifts: Vec<f32>,
238}
239
240#[derive(Clone, Debug)]
241pub struct RichGlyphQuad {
242    pub key: u64,
243    pub line_index: usize,
244    pub line_baseline: f32,
245    pub pen: [f32; 2],
246    pub atlas_page: AtlasPage,
247    pub is_color: bool,
248    pub rect: [f32; 4],
249    pub uv: [f32; 4],
250    pub origin: [f32; 2],
251    pub alpha: f32,
252    pub clip: [f32; 4],
253    pub turn: [f32; 3],
254    pub color: [f32; 4],
255}
256
257#[derive(Clone, Copy, Debug, PartialEq, Eq)]
258pub enum AtlasPage {
259    Monochrome,
260    Color,
261}
262
263#[derive(Clone, Copy, Debug, PartialEq, Eq)]
264pub struct AtlasCell {
265    pub page: AtlasPage,
266    pub rect: [u32; 4],
267}
268
269pub struct RichParagraph {
270    pub atlas: Atlas,
271    pub color_atlas: Option<Atlas>,
272    pub cells: BTreeMap<u64, AtlasCell>,
273    pub quads: Vec<RichGlyphQuad>,
274}
275
276#[derive(Clone, Copy, Debug, PartialEq)]
277pub struct Placement {
278    pub scale: f32,
279    pub representation: Representation,
280    pub origin: [f32; 2],
281    pub alpha: f32,
282    pub clip: [f32; 4],
283    pub turn: [f32; 3],
284}
285
286#[derive(Clone, Debug)]
287pub struct PageQuad {
288    pub page: u32,
289    pub quad: RichGlyphQuad,
290}
291
292#[derive(Clone, Debug, Default)]
293pub struct Placed {
294    pub quads: Vec<PageQuad>,
295    pub width: f32,
296    pub height: f32,
297    pub baseline: f32,
298}
299
300#[derive(Debug, PartialEq, Eq)]
301pub enum Error {
302    InvalidFont,
303    InvalidStyle,
304    InvalidPath,
305    UnsupportedGlyph,
306    AtlasTooLarge,
307}
308
309pub struct TextEngine {
310    fonts: FontSystem,
311    context: ScaleContext,
312    blocks: HashMap<u64, Block>,
313    rasters: BTreeMap<RasterKey, CachedRaster>,
314    atlases: BTreeMap<Vec<RasterKey>, CachedAtlas>,
315    shared: SharedAtlas,
316    numbers: HashMap<u64, number::NumberFace>,
317    fallbacks: fallback::Fallbacks,
318    tick: u64,
319    frame_start: u64,
320    shaping_budget: u64,
321    shaping_evicted: u64,
322}
323
324pub const DEFAULT_SHAPING_BUDGET: u64 = 8 << 20;
325
326#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
327pub struct ShapingStats {
328    pub blocks: usize,
329    pub number_faces: usize,
330    pub bytes: u64,
331    pub budget: u64,
332    pub evicted: u64,
333}
334
335struct CachedRaster {
336    mono: Option<GlyphImage>,
337    color: Option<GlyphImage>,
338    used: bool,
339}
340
341struct CachedAtlas {
342    used: bool,
343    mono: Atlas,
344    color: Option<Atlas>,
345    rects: BTreeMap<RasterKey, AtlasCell>,
346    cells: BTreeMap<u64, AtlasCell>,
347}
348
349struct PendingGlyph {
350    key: RasterKey,
351    font_id: fontdb::ID,
352    font_weight: fontdb::Weight,
353    span_index: usize,
354    font_size: f32,
355    pixel: [i32; 2],
356    pen: [f32; 2],
357    line_index: usize,
358    line_baseline: f32,
359}
360
361impl TextEngine {
362    pub fn new(font_bytes: &[u8]) -> Result<Self, Error> {
363        let mut db = fontdb::Database::new();
364        db.load_font_data(font_bytes.to_vec());
365        if db.faces().next().is_none() {
366            return Err(Error::InvalidFont);
367        }
368        Ok(Self {
369            fonts: FontSystem::new_with_locale_and_db_and_fallback(
370                "en-US".into(),
371                db,
372                fallback::Explicit,
373            ),
374            context: ScaleContext::new(),
375            blocks: HashMap::new(),
376            rasters: BTreeMap::new(),
377            atlases: BTreeMap::new(),
378            shared: SharedAtlas::new(),
379            numbers: HashMap::new(),
380            fallbacks: fallback::Fallbacks::default(),
381            tick: 0,
382            frame_start: 0,
383            shaping_budget: DEFAULT_SHAPING_BUDGET,
384            shaping_evicted: 0,
385        })
386    }
387
388    pub fn register_font(&mut self, font_bytes: &[u8]) -> Result<(), Error> {
389        let before = self.fonts.db().faces().count();
390        self.fonts.db_mut().load_font_data(font_bytes.to_vec());
391        if self.fonts.db().faces().count() == before {
392            return Err(Error::InvalidFont);
393        }
394        self.blocks.clear();
395        self.rasters.clear();
396        self.atlases.clear();
397        self.shared.clear();
398        self.numbers.clear();
399        Ok(())
400    }
401
402    pub fn begin_frame(&mut self) {
403        self.frame_start = self.tick;
404        for raster in self.rasters.values_mut() {
405            raster.used = false;
406        }
407        for atlas in self.atlases.values_mut() {
408            atlas.used = false;
409        }
410        self.shared.begin_frame();
411    }
412
413    pub fn end_frame(&mut self) {
414        self.rasters.retain(|_, raster| raster.used);
415        self.atlases.retain(|_, atlas| atlas.used);
416        let mut bytes = self.shaping_bytes();
417        if bytes <= self.shaping_budget {
418            return;
419        }
420        let start = self.frame_start;
421        let mut idle = self
422            .blocks
423            .iter()
424            .filter(|(_, block)| block.stamp <= start)
425            .map(|(&key, block)| (block.stamp, false, key))
426            .chain(
427                self.numbers
428                    .iter()
429                    .filter(|(_, face)| face.stamp <= start)
430                    .map(|(&key, face)| (face.stamp, true, key)),
431            )
432            .collect::<Vec<_>>();
433        idle.sort_unstable();
434        for (_, number, key) in idle {
435            if bytes <= self.shaping_budget {
436                break;
437            }
438            let freed = if number {
439                self.numbers.remove(&key).map_or(0, |face| face.bytes)
440            } else {
441                self.blocks.remove(&key).map_or(0, |block| block.bytes)
442            };
443            bytes -= freed;
444            self.shaping_evicted += 1;
445        }
446    }
447
448    pub fn set_shaping_budget(&mut self, bytes: u64) {
449        self.shaping_budget = bytes;
450    }
451
452    pub fn shaping_stats(&self) -> ShapingStats {
453        ShapingStats {
454            blocks: self.blocks.len(),
455            number_faces: self.numbers.len(),
456            bytes: self.shaping_bytes(),
457            budget: self.shaping_budget,
458            evicted: self.shaping_evicted,
459        }
460    }
461
462    fn shaping_bytes(&self) -> u64 {
463        self.blocks.values().map(|block| block.bytes).sum::<u64>()
464            + self.numbers.values().map(|face| face.bytes).sum::<u64>()
465    }
466
467    fn stamp(&mut self) -> u64 {
468        self.tick += 1;
469        self.tick
470    }
471
472    pub fn cached_atlases(&self) -> usize {
473        self.atlases.len()
474    }
475
476    pub fn set_atlas_budget(&mut self, bytes: u64) {
477        self.shared.set_budget(bytes);
478    }
479
480    pub fn set_atlas_page(&mut self, size: u32) -> Result<(), Error> {
481        self.shared.set_page_size(size)
482    }
483
484    pub fn atlas_stats(&self) -> AtlasStats {
485        self.shared.stats()
486    }
487
488    pub fn take_atlas_changes(&mut self) -> AtlasChanges {
489        self.shared.take_changes()
490    }
491
492    pub fn atlas_page(&self, id: u32) -> Option<&Atlas> {
493        self.shared.page(id)
494    }
495
496    pub fn cached_blocks(&self) -> usize {
497        self.blocks.len()
498    }
499
500    pub fn cached_rasters(&self) -> usize {
501        self.rasters.len()
502    }
503
504    pub fn layout_spans(
505        &mut self,
506        spans: &[Span],
507        width: Option<f32>,
508        scale: f32,
509        anchor: Anchor,
510    ) -> Result<&Block, Error> {
511        let key = self.layout_block(spans, width, scale, anchor, None, None)?;
512        Ok(&self.blocks[&key])
513    }
514
515    fn layout_block(
516        &mut self,
517        spans: &[Span],
518        width: Option<f32>,
519        scale: f32,
520        anchor: Anchor,
521        forms: Option<NumberForms>,
522        columns: Option<number::Columns>,
523    ) -> Result<u64, Error> {
524        if !scale.is_finite()
525            || scale <= 0.0
526            || width.is_some_and(|w| !w.is_finite() || w <= 0.0)
527            || spans.iter().any(|span| {
528                let face = &span.face;
529                !face.size.is_finite()
530                    || face.size <= 0.0
531                    || !face.line.is_finite()
532                    || face.line <= 0.0
533                    || !face.spacing.is_finite()
534                    || span.color.iter().any(|c| !c.is_finite())
535            })
536        {
537            return Err(Error::InvalidStyle);
538        }
539        let key = span_key(spans, width, scale, anchor, forms);
540        let stamp = self.stamp();
541        if let Some(block) = self.blocks.get_mut(&key) {
542            block.stamp = stamp;
543            return Ok(key);
544        }
545        self.shared.stats.shaped += 1;
546        let default_face = Face {
547            family: "serif".into(),
548            size: 16.0,
549            line: 22.0,
550            weight: 400,
551            italic: false,
552            spacing: 0.0,
553        };
554        let first = spans.first().map_or(&default_face, |span| &span.face);
555        let mut buffer = Buffer::new(
556            &mut self.fonts,
557            Metrics::new(first.size * scale, first.line * scale),
558        );
559        buffer.set_wrap(
560            &mut self.fonts,
561            if width.is_some() {
562                Wrap::WordOrGlyph
563            } else {
564                Wrap::None
565            },
566        );
567        buffer.set_size(&mut self.fonts, width.map(|w| w * scale), None);
568        let default = face_attrs(first, scale, 0, forms);
569        let align = match anchor {
570            Anchor::Start => None,
571            Anchor::Center => Some(Align::Center),
572            Anchor::End => Some(Align::End),
573        };
574        let laid = self.split(spans);
575        let attrs = laid.attrs(scale, forms);
576        buffer.set_rich_text(
577            &mut self.fonts,
578            laid.texts
579                .iter()
580                .zip(attrs)
581                .map(|((span, range), attrs)| (&spans[*span].text[range.clone()], attrs)),
582            &default,
583            Shaping::Advanced,
584            align,
585        );
586        buffer.shape_until_scroll(&mut self.fonts, false);
587        let lines = self.line_baselines(buffer.layout_runs(), &laid.pieces);
588        let mut w = 0.0f32;
589        let mut h = 0.0f32;
590        for run in buffer.layout_runs() {
591            w = w.max(run.line_w);
592            h = h.max(run.line_top + run.line_height);
593        }
594        let baseline = lines.first().copied();
595        let (shifts, w) = match (columns, spans.first()) {
596            (Some(columns), Some(span)) => self.column_shifts(
597                &buffer,
598                columns,
599                &span.face,
600                scale,
601                forms.unwrap_or_default(),
602            ),
603            _ => (Vec::new(), w),
604        };
605        let bytes = block_bytes(&buffer, spans.len(), lines.len(), shifts.len());
606        self.blocks.insert(
607            key,
608            Block {
609                buffer,
610                scale,
611                width: w / scale,
612                height: h / scale,
613                baseline: baseline.unwrap_or(h) / scale,
614                stamp,
615                bytes,
616                spans: laid.pieces.iter().map(|piece| piece.span).collect(),
617                lines,
618                shifts,
619            },
620        );
621        Ok(key)
622    }
623
624    #[allow(clippy::too_many_arguments)]
625    pub fn render_spans(
626        &mut self,
627        spans: &[Span],
628        width: Option<f32>,
629        scale: f32,
630        anchor: Anchor,
631        representation: Representation,
632        origin: [f32; 2],
633        alpha: f32,
634        clip: [f32; 4],
635        turn: [f32; 3],
636    ) -> Result<RichParagraph, Error> {
637        let key = self.layout_block(spans, width, scale, anchor, None, None)?;
638        let block = &self.blocks[&key];
639        let mut pending = Vec::new();
640        for (run, &line_y) in block.buffer.layout_runs().zip(&block.lines) {
641            for glyph in run.glyphs {
642                let span_index = block.span_of(glyph.metadata);
643                let ppem = match representation {
644                    Representation::Coverage => glyph.font_size,
645                    Representation::Msdf => 64.0,
646                };
647                if !(1.0..=4096.0).contains(&ppem) {
648                    return Err(Error::InvalidStyle);
649                }
650                let physical = glyph.physical((origin[0] * scale, origin[1] * scale), 1.0);
651                let pen_x =
652                    (glyph.x + glyph.x_offset * glyph.font_size).mul_add(1.0, origin[0] * scale);
653                let pen_y = glyph.y - glyph.y_offset * glyph.font_size + origin[1] * scale + line_y;
654                let font = self
655                    .fonts
656                    .get_font(glyph.font_id, glyph.font_weight)
657                    .ok_or(Error::UnsupportedGlyph)?;
658                let flags = raster_flags(
659                    glyph.cache_key_flags,
660                    spans.get(span_index).is_some_and(|span| span.face.italic),
661                    self.fonts.db().face(glyph.font_id).map(|face| face.style),
662                );
663                let key = raster_key(
664                    glyph.font_id,
665                    glyph.glyph_id,
666                    ppem,
667                    glyph.font_weight,
668                    glyph.font_size / scale,
669                    flags,
670                    font.as_swash(),
671                );
672                pending.push(PendingGlyph {
673                    key: RasterKey {
674                        representation,
675                        x_bin: physical.cache_key.x_bin,
676                        display_size: glyph.font_size.to_bits(),
677                        ..key
678                    },
679                    font_id: glyph.font_id,
680                    font_weight: glyph.font_weight,
681                    span_index,
682                    font_size: glyph.font_size,
683                    pixel: [physical.x, pen_y.trunc() as i32],
684                    pen: [pen_x / scale, pen_y / scale],
685                    line_index: run.line_i,
686                    line_baseline: line_y / scale + origin[1],
687                });
688            }
689        }
690        let atlas_key = pending
691            .iter()
692            .map(|glyph| glyph.key.clone())
693            .collect::<BTreeSet<_>>()
694            .into_iter()
695            .collect::<Vec<_>>();
696        for key in &atlas_key {
697            if let Some(raster) = self.rasters.get_mut(key) {
698                raster.used = true;
699            } else {
700                let glyph = pending.iter().find(|glyph| &glyph.key == key).unwrap();
701                let font = self
702                    .fonts
703                    .get_font(glyph.font_id, glyph.font_weight)
704                    .ok_or(Error::UnsupportedGlyph)?;
705                let ppem = f32::from_bits(key.ppem);
706                let mut scaler = self
707                    .context
708                    .builder(font.as_swash())
709                    .size(ppem)
710                    .hint(representation == Representation::Coverage && !key.disable_hinting)
711                    .variations(key.variations())
712                    .build();
713                let source_offset = match representation {
714                    Representation::Coverage => 1.0,
715                    Representation::Msdf => 64.0 / f32::from_bits(key.display_size),
716                };
717                let offset = [key.x_bin.as_float() * source_offset, 0.0];
718                let color = color_image(&mut scaler, key.glyph_id, key.fake_italic, offset);
719                let mono = if color.is_none() {
720                    match representation {
721                        Representation::Coverage => {
722                            coverage_image(&mut scaler, key.glyph_id, key.fake_italic, offset)
723                        }
724                        Representation::Msdf => {
725                            msdf_image(&mut scaler, key.glyph_id, key.fake_italic, offset)
726                        }
727                    }
728                } else {
729                    None
730                };
731                self.rasters.insert(
732                    key.clone(),
733                    CachedRaster {
734                        mono,
735                        color,
736                        used: true,
737                    },
738                );
739            }
740        }
741        if let Some(cached) = self.atlases.get_mut(&atlas_key) {
742            cached.used = true;
743        } else {
744            let images = atlas_key
745                .iter()
746                .map(|key| (key.clone(), self.rasters[key].mono.clone()))
747                .collect::<BTreeMap<_, _>>();
748            let color_images = atlas_key
749                .iter()
750                .map(|key| (key.clone(), self.rasters[key].color.clone()))
751                .collect::<BTreeMap<_, _>>();
752            let channels = if representation == Representation::Coverage {
753                1
754            } else {
755                3
756            };
757            let (mono, mono_rects) = pack(&images, channels, Some(4))?;
758            let (color, color_rects) = if color_images.values().any(Option::is_some) {
759                let (atlas, rects) = pack(&color_images, 4, Some(4))?;
760                (Some(atlas), rects)
761            } else {
762                (None, BTreeMap::new())
763            };
764            let mut rects = BTreeMap::new();
765            let mut cells = BTreeMap::new();
766            for key in &atlas_key {
767                let cell = if let Some(&rect) = color_rects.get(key) {
768                    AtlasCell {
769                        page: AtlasPage::Color,
770                        rect,
771                    }
772                } else if let Some(&rect) = mono_rects.get(key) {
773                    AtlasCell {
774                        page: AtlasPage::Monochrome,
775                        rect,
776                    }
777                } else {
778                    continue;
779                };
780                rects.insert(key.clone(), cell);
781                cells.insert(key.cell_key(), cell);
782            }
783            self.atlases.insert(
784                atlas_key.clone(),
785                CachedAtlas {
786                    used: true,
787                    mono,
788                    color,
789                    rects,
790                    cells,
791                },
792            );
793        }
794        let cached = &self.atlases[&atlas_key];
795        let mut quads = Vec::with_capacity(pending.len());
796        for glyph in pending {
797            let Some(&cell) = cached.rects.get(&glyph.key) else {
798                continue;
799            };
800            let raster = &self.rasters[&glyph.key];
801            let (image, target_atlas) = match cell.page {
802                AtlasPage::Monochrome => (raster.mono.as_ref().unwrap(), &cached.mono),
803                AtlasPage::Color => (
804                    raster.color.as_ref().unwrap(),
805                    cached.color.as_ref().unwrap(),
806                ),
807            };
808            let rect = cell.rect;
809            let factor = match representation {
810                Representation::Coverage => 1.0 / scale,
811                Representation::Msdf => glyph.font_size / (64.0 * scale),
812            };
813            let color = spans
814                .get(glyph.span_index)
815                .map_or([0.0, 0.0, 0.0, 1.0], |s| s.color);
816            quads.push(RichGlyphQuad {
817                key: glyph.key.cell_key(),
818                line_index: glyph.line_index,
819                line_baseline: glyph.line_baseline,
820                pen: glyph.pen,
821                atlas_page: cell.page,
822                is_color: cell.page == AtlasPage::Color,
823                rect: [
824                    glyph.pixel[0] as f32 / scale + image.left * factor,
825                    glyph.pixel[1] as f32 / scale + image.top * factor,
826                    image.width as f32 * factor,
827                    image.height as f32 * factor,
828                ],
829                uv: [
830                    rect[0] as f32 / target_atlas.levels[0].width as f32,
831                    rect[1] as f32 / target_atlas.levels[0].height as f32,
832                    (rect[0] + rect[2]) as f32 / target_atlas.levels[0].width as f32,
833                    (rect[1] + rect[3]) as f32 / target_atlas.levels[0].height as f32,
834                ],
835                origin,
836                alpha,
837                clip,
838                turn,
839                color: [color[0], color[1], color[2], color[3] * alpha],
840            });
841        }
842        Ok(RichParagraph {
843            atlas: cached.mono.clone(),
844            color_atlas: cached.color.clone(),
845            cells: cached.cells.clone(),
846            quads,
847        })
848    }
849
850    pub fn place_spans(
851        &mut self,
852        spans: &[Span],
853        width: Option<f32>,
854        anchor: Anchor,
855        placement: &Placement,
856    ) -> Result<Placed, Error> {
857        let key = self.layout_block(spans, width, placement.scale, anchor, None, None)?;
858        if width.is_none() && anchor != Anchor::Start {
859            let placement = Placement {
860                origin: number::anchored(placement.origin, self.blocks[&key].width, anchor),
861                ..*placement
862            };
863            return self.place_block(key, spans, &placement);
864        }
865        self.place_block(key, spans, placement)
866    }
867
868    fn place_block(
869        &mut self,
870        key: u64,
871        spans: &[Span],
872        placement: &Placement,
873    ) -> Result<Placed, Error> {
874        let scale = placement.scale;
875        let block = &self.blocks[&key];
876        let (width, height, baseline) = (block.width, block.height, block.baseline);
877        let spots = self.block_spots(key, spans);
878        let mut quads = Vec::with_capacity(spots.len());
879        for spot in &spots {
880            let pen = Pen::new(spot, placement);
881            let key = self.spot_key(spot, placement.representation, scale)?;
882            let key = match placement.representation {
883                Representation::Coverage => RasterKey {
884                    x_bin: pen.bin,
885                    ..key
886                },
887                Representation::Msdf => key,
888            };
889            self.place_glyph(spot, &key, pen, placement, &mut quads)?;
890        }
891        Ok(Placed {
892            quads,
893            width,
894            height,
895            baseline,
896        })
897    }
898
899    fn block_spots(&self, key: u64, spans: &[Span]) -> Vec<Spot> {
900        let block = &self.blocks[&key];
901        let mut spots = Vec::new();
902        let mut shifts = block.shifts.iter();
903        for (run, &line_y) in block.buffer.layout_runs().zip(&block.lines) {
904            for glyph in run.glyphs {
905                let shift = shifts.next().copied().unwrap_or_default();
906                let span_index = block.span_of(glyph.metadata);
907                let flags = raster_flags(
908                    glyph.cache_key_flags,
909                    spans.get(span_index).is_some_and(|span| span.face.italic),
910                    self.fonts.db().face(glyph.font_id).map(|face| face.style),
911                );
912                spots.push(Spot {
913                    font_id: glyph.font_id,
914                    font_weight: glyph.font_weight,
915                    glyph_id: glyph.glyph_id,
916                    flags,
917                    font_size: glyph.font_size,
918                    x: glyph.x + glyph.x_offset * glyph.font_size + shift,
919                    y: glyph.y - glyph.y_offset * glyph.font_size,
920                    line_y,
921                    line_index: run.line_i,
922                    color: spans
923                        .get(span_index)
924                        .map_or([0.0, 0.0, 0.0, 1.0], |span| span.color),
925                });
926            }
927        }
928        spots
929    }
930
931    fn spot_key(
932        &mut self,
933        spot: &Spot,
934        representation: Representation,
935        scale: f32,
936    ) -> Result<RasterKey, Error> {
937        let ppem = match representation {
938            Representation::Coverage => spot.font_size,
939            Representation::Msdf => field_ppem(spot.font_size),
940        };
941        if !(1.0..=4096.0).contains(&ppem) {
942            return Err(Error::InvalidStyle);
943        }
944        let font = self
945            .fonts
946            .get_font(spot.font_id, spot.font_weight)
947            .ok_or(Error::UnsupportedGlyph)?;
948        Ok(RasterKey {
949            representation,
950            ..raster_key(
951                spot.font_id,
952                spot.glyph_id,
953                ppem,
954                spot.font_weight,
955                spot.font_size / scale,
956                spot.flags,
957                font.as_swash(),
958            )
959        })
960    }
961
962    fn place_glyph(
963        &mut self,
964        spot: &Spot,
965        key: &RasterKey,
966        pen: Pen,
967        placement: &Placement,
968        quads: &mut Vec<PageQuad>,
969    ) -> Result<(), Error> {
970        let cell = match self.shared.touch(key) {
971            Some(cell) => cell,
972            None => {
973                let (kind, image) = self.shared_raster(key, spot.font_id, spot.font_weight)?;
974                self.shared.insert(key.clone(), kind, image)?
975            }
976        };
977        let Some(cell) = cell else {
978            return Ok(());
979        };
980        quads.push(PageQuad {
981            page: cell.page,
982            quad: glyph_quad(spot, cell, key.cell_key(), pen, placement),
983        });
984        Ok(())
985    }
986
987    fn shared_raster(
988        &mut self,
989        key: &RasterKey,
990        font_id: fontdb::ID,
991        font_weight: fontdb::Weight,
992    ) -> Result<(PageKind, Option<GlyphImage>), Error> {
993        let font = self
994            .fonts
995            .get_font(font_id, font_weight)
996            .ok_or(Error::UnsupportedGlyph)?;
997        Ok(raster(&mut self.context, &font, key))
998    }
999
1000    pub fn layout(
1001        &mut self,
1002        text: &str,
1003        style: Style<'_>,
1004        baseline: &Baseline,
1005    ) -> Result<Paragraph, Error> {
1006        if !style.size.is_finite()
1007            || style.size <= 0.0
1008            || !style.line_height.is_finite()
1009            || style.line_height <= 0.0
1010            || !style.pixels_per_unit.is_finite()
1011            || style.pixels_per_unit <= 0.0
1012            || style.wrap_width.is_some_and(|w| !w.is_finite() || w <= 0.0)
1013        {
1014            return Err(Error::InvalidStyle);
1015        }
1016        if baseline.sample(0.0, 0.0).is_none() {
1017            return Err(Error::InvalidPath);
1018        }
1019        let shape_scale = 1024.0;
1020        let mut buffer = Buffer::new(
1021            &mut self.fonts,
1022            Metrics::new(style.size * shape_scale, style.line_height * shape_scale),
1023        );
1024        buffer.set_wrap(
1025            &mut self.fonts,
1026            if style.wrap_width.is_some() {
1027                Wrap::WordOrGlyph
1028            } else {
1029                Wrap::None
1030            },
1031        );
1032        buffer.set_size(
1033            &mut self.fonts,
1034            style.wrap_width.map(|w| w * shape_scale),
1035            None,
1036        );
1037        buffer.set_text(
1038            &mut self.fonts,
1039            text,
1040            &Attrs::new().family(Family::Name(style.family)),
1041            Shaping::Advanced,
1042            None,
1043        );
1044        buffer.shape_until_scroll(&mut self.fonts, false);
1045        let first_line_y = buffer.layout_runs().next().map_or(0.0, |r| r.line_y);
1046        let mut pending = Vec::new();
1047        let mut width = 0.0f32;
1048        let mut height = 0.0f32;
1049        for run in buffer.layout_runs() {
1050            width = width.max(run.line_w / shape_scale);
1051            height = height.max((run.line_top + run.line_height) / shape_scale);
1052            for glyph in run.glyphs {
1053                let x = (glyph.x + glyph.x_offset * glyph.font_size) / shape_scale;
1054                let y = (run.line_y - first_line_y + glyph.y - glyph.y_offset * glyph.font_size)
1055                    / shape_scale;
1056                let (position, rotation) = baseline.sample(x, y).ok_or(Error::InvalidPath)?;
1057                pending.push((
1058                    glyph.font_id,
1059                    glyph.glyph_id,
1060                    glyph.font_weight,
1061                    glyph.start..glyph.end,
1062                    position,
1063                    rotation,
1064                ));
1065            }
1066        }
1067        drop(buffer);
1068        let ppem = match style.representation {
1069            Representation::Coverage => style.size * style.pixels_per_unit,
1070            Representation::Msdf => 64.0,
1071        };
1072        if !(1.0..=4096.0).contains(&ppem) {
1073            return Err(Error::InvalidStyle);
1074        }
1075        let mut images = BTreeMap::new();
1076        for (font_id, glyph_id, weight, _, _, _) in &pending {
1077            let key = (format!("{font_id:?}"), *glyph_id);
1078            if images.contains_key(&key) {
1079                continue;
1080            }
1081            let font = self
1082                .fonts
1083                .get_font(*font_id, *weight)
1084                .ok_or(Error::UnsupportedGlyph)?;
1085            let mut scaler = self
1086                .context
1087                .builder(font.as_swash())
1088                .size(ppem)
1089                .hint(false)
1090                .build();
1091            let image = match style.representation {
1092                Representation::Coverage => coverage_image(&mut scaler, *glyph_id, false, [0.0; 2]),
1093                Representation::Msdf => msdf_image(&mut scaler, *glyph_id, false, [0.0; 2]),
1094            };
1095            images.insert(key, image);
1096        }
1097        let channels = if style.representation == Representation::Coverage {
1098            1
1099        } else {
1100            3
1101        };
1102        let (atlas, rects) = pack(&images, channels, None)?;
1103        let mut glyphs = Vec::new();
1104        for (font_id, glyph_id, _, cluster, position, rotation) in pending {
1105            let key = (format!("{font_id:?}"), glyph_id);
1106            let Some(image) = images.get(&key).and_then(Option::as_ref) else {
1107                continue;
1108            };
1109            let rect = rects[&key];
1110            let scale = if style.representation == Representation::Coverage {
1111                1.0 / style.pixels_per_unit
1112            } else {
1113                style.size / 64.0
1114            };
1115            glyphs.push(GlyphInstance {
1116                glyph_id,
1117                cluster,
1118                position,
1119                rotation,
1120                scale,
1121                local_rect: [
1122                    image.left,
1123                    image.top,
1124                    image.width as f32,
1125                    image.height as f32,
1126                ],
1127                atlas_rect: rect,
1128                uv: [
1129                    rect[0] as f32 / atlas.levels[0].width as f32,
1130                    rect[1] as f32 / atlas.levels[0].height as f32,
1131                    (rect[0] + rect[2]) as f32 / atlas.levels[0].width as f32,
1132                    (rect[1] + rect[3]) as f32 / atlas.levels[0].height as f32,
1133                ],
1134            });
1135        }
1136        Ok(Paragraph {
1137            glyphs,
1138            atlas,
1139            width,
1140            height,
1141        })
1142    }
1143}
1144
1145fn raster(
1146    context: &mut ScaleContext,
1147    font: &cosmic_text::Font,
1148    key: &RasterKey,
1149) -> (PageKind, Option<GlyphImage>) {
1150    let representation = key.representation;
1151    let mut scaler = context
1152        .builder(font.as_swash())
1153        .size(f32::from_bits(key.ppem))
1154        .hint(representation == Representation::Coverage && !key.disable_hinting)
1155        .variations(key.variations())
1156        .build();
1157    let offset = [key.x_bin.as_float(), 0.0];
1158    if let Some(color) = color_image(&mut scaler, key.glyph_id, key.fake_italic, offset) {
1159        return (PageKind::Color, Some(color));
1160    }
1161    match representation {
1162        Representation::Coverage => (
1163            PageKind::Coverage,
1164            coverage_image(&mut scaler, key.glyph_id, key.fake_italic, offset),
1165        ),
1166        Representation::Msdf => (
1167            PageKind::Msdf,
1168            msdf_field(&mut scaler, key.glyph_id, key.fake_italic, offset, true),
1169        ),
1170    }
1171}
1172
1173pub fn field_ppem(display_size: f32) -> f32 {
1174    if display_size > FINE_MSDF_FROM {
1175        FINE_MSDF_FIELD
1176    } else {
1177        MSDF_FIELD
1178    }
1179}
1180
1181#[derive(Clone, Copy)]
1182struct Pen {
1183    x: f32,
1184    y: f32,
1185    pixel_x: i32,
1186    bin: SubpixelBin,
1187}
1188
1189impl Pen {
1190    fn new(spot: &Spot, placement: &Placement) -> Self {
1191        let x = spot.x.mul_add(1.0, placement.origin[0] * placement.scale);
1192        let y = spot.y + placement.origin[1] * placement.scale + spot.line_y;
1193        let (pixel_x, bin) = SubpixelBin::new(x);
1194        Self { x, y, pixel_x, bin }
1195    }
1196}
1197
1198fn glyph_quad(
1199    spot: &Spot,
1200    cell: CellRef,
1201    key: u64,
1202    pen: Pen,
1203    placement: &Placement,
1204) -> RichGlyphQuad {
1205    let scale = placement.scale;
1206    let (x, factor) = match placement.representation {
1207        Representation::Coverage => (pen.pixel_x as f32 / scale, 1.0 / scale),
1208        Representation::Msdf => (
1209            pen.x / scale,
1210            spot.font_size / (field_ppem(spot.font_size) * scale),
1211        ),
1212    };
1213    let size = cell.size as f32;
1214    let rect = cell.rect;
1215    let page = if cell.kind == PageKind::Color {
1216        AtlasPage::Color
1217    } else {
1218        AtlasPage::Monochrome
1219    };
1220    let color = spot.color;
1221    RichGlyphQuad {
1222        key,
1223        line_index: spot.line_index,
1224        line_baseline: spot.line_y / scale + placement.origin[1],
1225        pen: [pen.x / scale, pen.y / scale],
1226        atlas_page: page,
1227        is_color: page == AtlasPage::Color,
1228        rect: [
1229            x + cell.left * factor,
1230            pen.y.trunc() / scale + cell.top * factor,
1231            rect[2] as f32 * factor,
1232            rect[3] as f32 * factor,
1233        ],
1234        uv: [
1235            rect[0] as f32 / size,
1236            rect[1] as f32 / size,
1237            (rect[0] + rect[2]) as f32 / size,
1238            (rect[1] + rect[3]) as f32 / size,
1239        ],
1240        origin: placement.origin,
1241        alpha: placement.alpha,
1242        clip: placement.clip,
1243        turn: placement.turn,
1244        color: [color[0], color[1], color[2], color[3] * placement.alpha],
1245    }
1246}
1247
1248#[derive(Clone)]
1249struct Spot {
1250    font_id: fontdb::ID,
1251    font_weight: fontdb::Weight,
1252    glyph_id: u16,
1253    flags: CacheKeyFlags,
1254    font_size: f32,
1255    x: f32,
1256    y: f32,
1257    line_y: f32,
1258    line_index: usize,
1259    color: [f32; 4],
1260}
1261
1262fn face_attrs(face: &Face, scale: f32, metadata: usize, forms: Option<NumberForms>) -> Attrs<'_> {
1263    let mut attrs = Attrs::new()
1264        .family(Family::Name(&face.family))
1265        .weight(fontdb::Weight(face.weight))
1266        .metrics(Metrics::new(face.size * scale, face.line * scale))
1267        .metadata(metadata)
1268        .letter_spacing(face.spacing);
1269    if let Some(forms) = forms {
1270        let mut features = FontFeatures::new();
1271        if forms.figures == Figures::Tabular {
1272            features.enable(FeatureTag::new(b"tnum"));
1273        }
1274        if forms.ligatures == Ligatures::Off {
1275            features.disable(FeatureTag::STANDARD_LIGATURES);
1276            features.disable(FeatureTag::CONTEXTUAL_LIGATURES);
1277        }
1278        attrs = attrs.font_features(features);
1279    }
1280    if face.italic {
1281        attrs.style(cosmic_text::Style::Italic)
1282    } else {
1283        attrs
1284    }
1285}
1286
1287fn span_key(
1288    spans: &[Span],
1289    width: Option<f32>,
1290    scale: f32,
1291    anchor: Anchor,
1292    forms: Option<NumberForms>,
1293) -> u64 {
1294    let mut hasher = std::collections::hash_map::DefaultHasher::new();
1295    for span in spans {
1296        span.text.hash(&mut hasher);
1297        span.face.family.hash(&mut hasher);
1298        span.face.size.to_bits().hash(&mut hasher);
1299        span.face.line.to_bits().hash(&mut hasher);
1300        span.face.weight.hash(&mut hasher);
1301        span.face.italic.hash(&mut hasher);
1302        span.face.spacing.to_bits().hash(&mut hasher);
1303        for channel in span.color {
1304            channel.to_bits().hash(&mut hasher);
1305        }
1306    }
1307    width.map(f32::to_bits).hash(&mut hasher);
1308    scale.to_bits().hash(&mut hasher);
1309    anchor.hash(&mut hasher);
1310    forms.hash(&mut hasher);
1311    hasher.finish()
1312}
1313
1314fn block_bytes(buffer: &Buffer, spans: usize, lines: usize, shifts: usize) -> u64 {
1315    let text = buffer
1316        .lines
1317        .iter()
1318        .map(|line| line.text().len() + std::mem::size_of::<cosmic_text::BufferLine>())
1319        .sum::<usize>();
1320    let glyphs = buffer
1321        .layout_runs()
1322        .map(|run| run.glyphs.len())
1323        .sum::<usize>();
1324    (std::mem::size_of::<Block>()
1325        + text
1326        + glyphs
1327            * (std::mem::size_of::<cosmic_text::ShapeGlyph>()
1328                + std::mem::size_of::<cosmic_text::LayoutGlyph>())
1329        + lines
1330            * (std::mem::size_of::<cosmic_text::LayoutLine>()
1331                + std::mem::size_of::<cosmic_text::ShapeLine>()
1332                + std::mem::size_of::<f32>())
1333        + spans * (std::mem::size_of::<cosmic_text::AttrsOwned>() + std::mem::size_of::<usize>())
1334        + shifts * std::mem::size_of::<f32>()) as u64
1335        * 7
1336        / 4
1337}
1338
1339impl Block {
1340    fn span_of(&self, metadata: usize) -> usize {
1341        self.spans.get(metadata).copied().unwrap_or(metadata)
1342    }
1343
1344    pub fn line_baselines(&self) -> Vec<f32> {
1345        self.lines.iter().map(|y| y / self.scale).collect()
1346    }
1347}
1348
1349pub fn hit(block: &Block, x: f32, y: f32) -> Option<usize> {
1350    let cursor = block.buffer.hit(x * block.scale, y * block.scale)?;
1351    Some(
1352        block
1353            .buffer
1354            .lines
1355            .iter()
1356            .take(cursor.line)
1357            .map(|line| line.text().len() + 1)
1358            .sum::<usize>()
1359            + cursor.index,
1360    )
1361}
1362
1363pub fn cursor_at(block: &Block, offset: usize) -> (usize, usize) {
1364    let mut left = offset;
1365    for (line, text) in block.buffer.lines.iter().enumerate() {
1366        let len = text.text().len();
1367        if left <= len {
1368            return (line, left);
1369        }
1370        left = left.saturating_sub(len + 1);
1371    }
1372    let line = block.buffer.lines.len().saturating_sub(1);
1373    (
1374        line,
1375        block
1376            .buffer
1377            .lines
1378            .last()
1379            .map_or(0, |text| text.text().len()),
1380    )
1381}
1382
1383pub fn marks(block: &Block, from: usize, to: usize) -> Vec<[f32; 4]> {
1384    let (start_line, start_index) = cursor_at(block, from.min(to));
1385    let (end_line, end_index) = cursor_at(block, from.max(to));
1386    let start = Cursor::new(start_line, start_index);
1387    let end = Cursor::new(end_line, end_index);
1388    block
1389        .buffer
1390        .layout_runs()
1391        .filter_map(|run| {
1392            let (x, width) = run.highlight(start, end)?;
1393            (width > 0.0).then_some([
1394                x / block.scale,
1395                run.line_top / block.scale,
1396                width / block.scale,
1397                run.line_height / block.scale,
1398            ])
1399        })
1400        .collect()
1401}
1402
1403pub fn caret(block: &Block, offset: usize) -> Option<[f32; 4]> {
1404    let (line, index) = cursor_at(block, offset);
1405    let mut end = None;
1406    for run in block.buffer.layout_runs().filter(|run| run.line_i == line) {
1407        for glyph in run.glyphs {
1408            if glyph.start <= index && index < glyph.end {
1409                let x = if run.rtl { glyph.x + glyph.w } else { glyph.x };
1410                return Some([
1411                    x / block.scale,
1412                    run.line_top / block.scale,
1413                    0.0,
1414                    run.line_height / block.scale,
1415                ]);
1416            }
1417        }
1418        let x = run.glyphs.last().map_or(
1419            0.0,
1420            |glyph| {
1421                if run.rtl { glyph.x } else { glyph.x + glyph.w }
1422            },
1423        );
1424        end = Some([
1425            x / block.scale,
1426            run.line_top / block.scale,
1427            0.0,
1428            run.line_height / block.scale,
1429        ]);
1430    }
1431    end
1432}
1433
1434pub fn baseline(block: &Block) -> f32 {
1435    block.baseline
1436}
1437
1438#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1439struct RasterKey {
1440    font_id: String,
1441    glyph_id: u16,
1442    representation: Representation,
1443    ppem: u32,
1444    display_size: u32,
1445    weight: u16,
1446    wght: Option<u32>,
1447    opsz: Option<u32>,
1448    fake_italic: bool,
1449    disable_hinting: bool,
1450    x_bin: SubpixelBin,
1451    y_bin: SubpixelBin,
1452}
1453
1454impl RasterKey {
1455    fn cell_key(&self) -> u64 {
1456        fn write(hash: &mut u64, bytes: &[u8]) {
1457            for byte in bytes {
1458                *hash = (*hash ^ u64::from(*byte)).wrapping_mul(0x100000001b3);
1459            }
1460        }
1461
1462        let mut hash = 0xcbf29ce484222325;
1463        write(&mut hash, &(self.font_id.len() as u64).to_le_bytes());
1464        write(&mut hash, self.font_id.as_bytes());
1465        write(&mut hash, &self.glyph_id.to_le_bytes());
1466        write(&mut hash, &[self.representation as u8]);
1467        write(&mut hash, &self.ppem.to_le_bytes());
1468        write(&mut hash, &self.display_size.to_le_bytes());
1469        write(&mut hash, &self.weight.to_le_bytes());
1470        for value in [self.wght, self.opsz] {
1471            write(&mut hash, &[u8::from(value.is_some())]);
1472            write(&mut hash, &value.unwrap_or_default().to_le_bytes());
1473        }
1474        write(&mut hash, &[u8::from(self.fake_italic)]);
1475        write(&mut hash, &[u8::from(self.disable_hinting)]);
1476        write(&mut hash, &[self.x_bin as u8, self.y_bin as u8]);
1477        hash
1478    }
1479
1480    fn variations(&self) -> impl Iterator<Item = swash::Setting<f32>> {
1481        [
1482            self.wght.map(|value| swash::Setting {
1483                tag: swash::Tag::from_be_bytes(*b"wght"),
1484                value: f32::from_bits(value),
1485            }),
1486            self.opsz.map(|value| swash::Setting {
1487                tag: swash::Tag::from_be_bytes(*b"opsz"),
1488                value: f32::from_bits(value),
1489            }),
1490        ]
1491        .into_iter()
1492        .flatten()
1493    }
1494}
1495
1496fn raster_key(
1497    font_id: fontdb::ID,
1498    glyph_id: u16,
1499    ppem: f32,
1500    weight: fontdb::Weight,
1501    size_points: f32,
1502    flags: CacheKeyFlags,
1503    font: swash::FontRef<'_>,
1504) -> RasterKey {
1505    let variations = font.variations();
1506    let wght = variations
1507        .find_by_tag(swash::Tag::from_be_bytes(*b"wght"))
1508        .map(|axis| {
1509            f32::from(weight.0)
1510                .clamp(axis.min_value(), axis.max_value())
1511                .to_bits()
1512        });
1513    let opsz = variations
1514        .find_by_tag(swash::Tag::from_be_bytes(*b"opsz"))
1515        .map(|axis| {
1516            size_points
1517                .clamp(axis.min_value(), axis.max_value())
1518                .to_bits()
1519        });
1520    RasterKey {
1521        font_id: format!("{font_id:?}"),
1522        glyph_id,
1523        representation: Representation::Coverage,
1524        ppem: ppem.to_bits(),
1525        display_size: ppem.to_bits(),
1526        weight: weight.0,
1527        wght,
1528        opsz,
1529        fake_italic: flags.contains(CacheKeyFlags::FAKE_ITALIC),
1530        disable_hinting: flags.contains(CacheKeyFlags::DISABLE_HINTING),
1531        x_bin: SubpixelBin::Zero,
1532        y_bin: SubpixelBin::Zero,
1533    }
1534}
1535
1536fn raster_flags(flags: CacheKeyFlags, italic: bool, style: Option<fontdb::Style>) -> CacheKeyFlags {
1537    if italic && style == Some(fontdb::Style::Normal) {
1538        flags | CacheKeyFlags::FAKE_ITALIC
1539    } else {
1540        flags
1541    }
1542}
1543
1544fn oblique_transform(fake_italic: bool) -> Option<Transform> {
1545    fake_italic.then(|| Transform::skew(Angle::from_degrees(14.0), Angle::from_degrees(0.0)))
1546}
1547
1548#[derive(Clone)]
1549struct GlyphImage {
1550    left: f32,
1551    top: f32,
1552    width: u32,
1553    height: u32,
1554    bytes: Vec<u8>,
1555}
1556
1557fn coverage_image(
1558    scaler: &mut swash::scale::Scaler<'_>,
1559    glyph_id: u16,
1560    fake_italic: bool,
1561    offset: [f32; 2],
1562) -> Option<GlyphImage> {
1563    let image = Render::new(&[Source::Outline])
1564        .transform(oblique_transform(fake_italic))
1565        .offset(Vector::new(offset[0], -offset[1]))
1566        .render(scaler, glyph_id)?;
1567    if image.placement.width == 0 || image.placement.height == 0 {
1568        return None;
1569    }
1570    Some(GlyphImage {
1571        left: image.placement.left as f32,
1572        top: -image.placement.top as f32,
1573        width: image.placement.width,
1574        height: image.placement.height,
1575        bytes: image.data,
1576    })
1577}
1578
1579fn color_image(
1580    scaler: &mut swash::scale::Scaler<'_>,
1581    glyph_id: u16,
1582    fake_italic: bool,
1583    offset: [f32; 2],
1584) -> Option<GlyphImage> {
1585    let mut image = Render::new(&[
1586        Source::ColorOutline(0),
1587        Source::ColorBitmap(StrikeWith::BestFit),
1588    ])
1589    .transform(oblique_transform(fake_italic))
1590    .offset(Vector::new(offset[0], -offset[1]))
1591    .render(scaler, glyph_id)?;
1592    if image.content != Content::Color || image.placement.width == 0 || image.placement.height == 0
1593    {
1594        return None;
1595    }
1596    if matches!(image.source, Source::ColorBitmap(_)) {
1597        let whole_x = offset[0].floor() as i32;
1598        let whole_y = offset[1].floor() as i32;
1599        let fraction_x = offset[0] - whole_x as f32;
1600        let fraction_y = offset[1] - whole_y as f32;
1601        image.placement.left += whole_x;
1602        image.placement.top -= whole_y;
1603        if fraction_x > 0.0 || fraction_y > 0.0 {
1604            let width = image.placement.width;
1605            let height = image.placement.height;
1606            let shifted_width = width + u32::from(fraction_x > 0.0);
1607            let shifted_height = height + u32::from(fraction_y > 0.0);
1608            let mut shifted = vec![0.0f32; (shifted_width * shifted_height * 4) as usize];
1609            for y in 0..height {
1610                for x in 0..width {
1611                    let source = ((y * width + x) * 4) as usize;
1612                    for dy in 0..=u32::from(fraction_y > 0.0) {
1613                        for dx in 0..=u32::from(fraction_x > 0.0) {
1614                            let weight = if dx == 0 {
1615                                1.0 - fraction_x
1616                            } else {
1617                                fraction_x
1618                            } * if dy == 0 {
1619                                1.0 - fraction_y
1620                            } else {
1621                                fraction_y
1622                            };
1623                            let target = (((y + dy) * shifted_width + x + dx) * 4) as usize;
1624                            for channel in 0..4 {
1625                                shifted[target + channel] +=
1626                                    image.data[source + channel] as f32 * weight;
1627                            }
1628                        }
1629                    }
1630                }
1631            }
1632            image.data = shifted
1633                .into_iter()
1634                .map(|value| value.round() as u8)
1635                .collect();
1636            image.placement.width = shifted_width;
1637            image.placement.height = shifted_height;
1638        }
1639    }
1640    Some(GlyphImage {
1641        left: image.placement.left as f32,
1642        top: -image.placement.top as f32,
1643        width: image.placement.width,
1644        height: image.placement.height,
1645        bytes: image.data,
1646    })
1647}
1648
1649fn msdf_image(
1650    scaler: &mut swash::scale::Scaler<'_>,
1651    glyph_id: u16,
1652    fake_italic: bool,
1653    offset: [f32; 2],
1654) -> Option<GlyphImage> {
1655    msdf_field(scaler, glyph_id, fake_italic, offset, false)
1656}
1657
1658type Contours = (Vec<Vec<[f32; 2]>>, [f32; 2], [u32; 2]);
1659
1660fn msdf_field(
1661    scaler: &mut swash::scale::Scaler<'_>,
1662    glyph_id: u16,
1663    fake_italic: bool,
1664    offset: [f32; 2],
1665    sharp: bool,
1666) -> Option<GlyphImage> {
1667    let (contours, [left, top], [width, height]) =
1668        glyph_contours(scaler, glyph_id, fake_italic, offset)?;
1669    if sharp {
1670        field::sharp(&contours, [left, top], [width, height])
1671    } else {
1672        field::soft(&contours, [left, top], [width, height])
1673    }
1674}
1675
1676fn glyph_contours(
1677    scaler: &mut swash::scale::Scaler<'_>,
1678    glyph_id: u16,
1679    fake_italic: bool,
1680    offset: [f32; 2],
1681) -> Option<Contours> {
1682    let mut outline = scaler.scale_outline(glyph_id)?;
1683    if let Some(transform) = oblique_transform(fake_italic) {
1684        outline.transform(&transform);
1685    }
1686    if offset != [0.0, 0.0] {
1687        outline.transform(&Transform::translation(offset[0], -offset[1]));
1688    }
1689    let bounds = outline.bounds();
1690    let left = bounds.min.x.floor() - 5.0;
1691    let top = -bounds.max.y.ceil() - 5.0;
1692    let width = (bounds.max.x.ceil() - left + 5.0) as u32;
1693    let height = (-bounds.min.y.ceil() - top + 5.0) as u32;
1694    if width == 0 || height == 0 {
1695        return None;
1696    }
1697    let mut contours: Vec<Vec<[f32; 2]>> = Vec::new();
1698    let mut points = Vec::new();
1699    let mut current = [0.0; 2];
1700    for command in outline.path().commands() {
1701        match command {
1702            Command::MoveTo(p) => {
1703                if points.len() > 1 {
1704                    contours.push(std::mem::take(&mut points));
1705                }
1706                current = [p.x, -p.y];
1707                points.push(current);
1708            }
1709            Command::LineTo(p) => {
1710                current = [p.x, -p.y];
1711                points.push(current);
1712            }
1713            Command::QuadTo(c, p) => {
1714                let a = current;
1715                for i in 1..=16 {
1716                    let t = i as f32 / 16.0;
1717                    let u = 1.0 - t;
1718                    points.push([
1719                        u * u * a[0] + 2.0 * u * t * c.x + t * t * p.x,
1720                        u * u * a[1] - 2.0 * u * t * c.y - t * t * p.y,
1721                    ]);
1722                }
1723                current = [p.x, -p.y];
1724            }
1725            Command::CurveTo(c, d, p) => {
1726                let a = current;
1727                for i in 1..=24 {
1728                    let t = i as f32 / 24.0;
1729                    let u = 1.0 - t;
1730                    points.push([
1731                        u * u * u * a[0]
1732                            + 3.0 * u * u * t * c.x
1733                            + 3.0 * u * t * t * d.x
1734                            + t * t * t * p.x,
1735                        u * u * u * a[1]
1736                            - 3.0 * u * u * t * c.y
1737                            - 3.0 * u * t * t * d.y
1738                            - t * t * t * p.y,
1739                    ]);
1740                }
1741                current = [p.x, -p.y];
1742            }
1743            Command::Close => {
1744                if points.len() > 1 {
1745                    contours.push(std::mem::take(&mut points));
1746                }
1747            }
1748        }
1749    }
1750    if points.len() > 1 {
1751        contours.push(points);
1752    }
1753    Some((contours, [left, top], [width, height]))
1754}
1755
1756fn pack<K: Ord + Clone>(
1757    images: &BTreeMap<K, Option<GlyphImage>>,
1758    channels: u32,
1759    mip_count: Option<usize>,
1760) -> Result<(Atlas, BTreeMap<K, [u32; 4]>), Error> {
1761    let width = 1024u32;
1762    let padding = ATLAS_PADDING;
1763    let mut x = 0u32;
1764    let mut y = 0u32;
1765    let mut row_height = 0u32;
1766    let mut rects = BTreeMap::new();
1767    for (key, image) in images {
1768        let Some(image) = image else {
1769            continue;
1770        };
1771        let cell_width = (image.width + padding * 2).next_multiple_of(ATLAS_CELL_ALIGN);
1772        let cell_height = (image.height + padding * 2).next_multiple_of(ATLAS_CELL_ALIGN);
1773        if cell_width > width {
1774            return Err(Error::AtlasTooLarge);
1775        }
1776        if x + cell_width > width {
1777            y += row_height;
1778            x = 0;
1779            row_height = 0;
1780        }
1781        rects.insert(
1782            key.clone(),
1783            [x + padding, y + padding, image.width, image.height],
1784        );
1785        x += cell_width;
1786        row_height = row_height.max(cell_height);
1787    }
1788    let height = (y + row_height).next_power_of_two().max(ATLAS_CELL_ALIGN);
1789    if height > 16384 {
1790        return Err(Error::AtlasTooLarge);
1791    }
1792    let mut bytes = vec![0; (width * height * channels) as usize];
1793    for (key, image) in images {
1794        let Some(image) = image else {
1795            continue;
1796        };
1797        let rect = rects[key];
1798        for dy in -(padding as i32)..(image.height + padding) as i32 {
1799            for dx in -(padding as i32)..(image.width + padding) as i32 {
1800                let src_x = dx.clamp(0, image.width as i32 - 1) as u32;
1801                let src_y = dy.clamp(0, image.height as i32 - 1) as u32;
1802                let dst_x = (rect[0] as i32 + dx) as u32;
1803                let dst_y = (rect[1] as i32 + dy) as u32;
1804                let src = ((src_y * image.width + src_x) * channels) as usize;
1805                let dst = ((dst_y * width + dst_x) * channels) as usize;
1806                bytes[dst..dst + channels as usize]
1807                    .copy_from_slice(&image.bytes[src..src + channels as usize]);
1808            }
1809        }
1810    }
1811    let mut levels = vec![MipLevel {
1812        width,
1813        height,
1814        bytes,
1815    }];
1816    while mip_count.map_or_else(
1817        || {
1818            levels
1819                .last()
1820                .is_some_and(|level| level.width > 1 || level.height > 1)
1821        },
1822        |count| levels.len() < count,
1823    ) {
1824        let prev = levels.last().unwrap();
1825        let w = (prev.width / 2).max(1);
1826        let h = (prev.height / 2).max(1);
1827        let mut next = vec![0; (w * h * channels) as usize];
1828        for row in 0..h {
1829            for col in 0..w {
1830                for channel in 0..channels {
1831                    let mut sum = 0u32;
1832                    for dy in 0..2 {
1833                        for dx in 0..2 {
1834                            let sx = (2 * col + dx).min(prev.width - 1);
1835                            let sy = (2 * row + dy).min(prev.height - 1);
1836                            sum += prev.bytes
1837                                [((sy * prev.width + sx) * channels + channel) as usize]
1838                                as u32;
1839                        }
1840                    }
1841                    next[((row * w + col) * channels + channel) as usize] = ((sum + 2) / 4) as u8;
1842                }
1843            }
1844        }
1845        levels.push(MipLevel {
1846            width: w,
1847            height: h,
1848            bytes: next,
1849        });
1850    }
1851    Ok((Atlas { channels, levels }, rects))
1852}
1853
1854#[cfg(test)]
1855mod tests {
1856    use super::*;
1857
1858    const FONT: &[u8] = include_bytes!("../fonts/EBGaramond[wght].ttf");
1859    const MONO_FONT: &[u8] = include_bytes!("../tests/fonts/IBMPlexMono-Regular.ttf");
1860    const VARIABLE_FONT: &[u8] = include_bytes!("../tests/fonts/DMSans[opsz,wght].ttf");
1861    const COLOR_FONT: &[u8] = include_bytes!("../tests/fonts/BungeeColor-Regular.ttf");
1862
1863    fn rendered_pixels(paragraph: &RichParagraph) -> (u32, u32, Vec<u8>) {
1864        let quad = &paragraph.quads[0];
1865        let level = &paragraph.atlas.levels[0];
1866        let x = (quad.uv[0] * level.width as f32).round() as u32;
1867        let y = (quad.uv[1] * level.height as f32).round() as u32;
1868        let width = (quad.uv[2] * level.width as f32).round() as u32 - x;
1869        let height = (quad.uv[3] * level.height as f32).round() as u32 - y;
1870        let mut pixels = Vec::new();
1871        for row in y..y + height {
1872            let start = (row * level.width + x) as usize;
1873            pixels.extend_from_slice(&level.bytes[start..start + width as usize]);
1874        }
1875        (width, height, pixels)
1876    }
1877
1878    fn first_stem_width(width: u32, height: u32, pixels: &[u8]) -> u32 {
1879        let row = height / 4;
1880        let ink: Vec<_> = (0..width)
1881            .filter(|&x| pixels[(row * width + x) as usize] >= 128)
1882            .collect();
1883        let first = ink[0];
1884        ink.into_iter()
1885            .enumerate()
1886            .take_while(|(index, x)| *x == first + *index as u32)
1887            .count() as u32
1888    }
1889
1890    #[test]
1891    fn variable_weight_matches_swash_and_grows_stems() {
1892        let mut engine = TextEngine::new(VARIABLE_FONT).unwrap();
1893        let mut areas = Vec::new();
1894        let mut stems = Vec::new();
1895        for weight in [400, 700] {
1896            let span = Span {
1897                text: "H".into(),
1898                face: Face {
1899                    family: "DM Sans".into(),
1900                    size: 64.0,
1901                    line: 72.0,
1902                    weight,
1903                    italic: false,
1904                    spacing: 0.0,
1905                },
1906                color: [1.0; 4],
1907            };
1908            let block = engine
1909                .layout_spans(std::slice::from_ref(&span), None, 1.0, Anchor::Start)
1910                .unwrap();
1911            let glyph = &block.buffer.layout_runs().next().unwrap().glyphs[0];
1912            let font_id = glyph.font_id;
1913            let glyph_id = glyph.glyph_id;
1914            let font_weight = glyph.font_weight;
1915            let flags = glyph.cache_key_flags;
1916            let font_size = glyph.font_size;
1917            let rendered = engine
1918                .render_spans(
1919                    &[span],
1920                    None,
1921                    1.0,
1922                    Anchor::Start,
1923                    Representation::Coverage,
1924                    [0.0; 2],
1925                    1.0,
1926                    [0.0; 4],
1927                    [0.0; 3],
1928                )
1929                .unwrap();
1930            let (width, height, pixels) = rendered_pixels(&rendered);
1931            let font = engine.fonts.get_font(font_id, font_weight).unwrap();
1932            let key = raster_key(
1933                font_id,
1934                glyph_id,
1935                font_size,
1936                font_weight,
1937                font_size,
1938                flags,
1939                font.as_swash(),
1940            );
1941            assert_eq!(key.wght, Some((weight as f32).to_bits()));
1942            assert!(key.opsz.is_some());
1943            let mut scaler = ScaleContext::new();
1944            let mut scaler = scaler
1945                .builder(font.as_swash())
1946                .size(font_size)
1947                .hint(!flags.contains(CacheKeyFlags::DISABLE_HINTING))
1948                .variations(key.variations())
1949                .build();
1950            let reference = Render::new(&[Source::Outline])
1951                .render(&mut scaler, glyph_id)
1952                .unwrap();
1953            assert_eq!(
1954                (width, height),
1955                (reference.placement.width, reference.placement.height)
1956            );
1957            assert_eq!(pixels, reference.data);
1958            areas.push(pixels.iter().map(|&value| u64::from(value)).sum::<u64>());
1959            stems.push(first_stem_width(width, height, &pixels));
1960        }
1961        assert!(areas[1] > areas[0]);
1962        assert!(stems[1] > stems[0], "stem widths: {stems:?}");
1963    }
1964
1965    #[test]
1966    fn optical_size_uses_unscaled_points_at_fixed_raster_size() {
1967        let mut engine = TextEngine::new(VARIABLE_FONT).unwrap();
1968        let mut keys = Vec::new();
1969        let mut images = Vec::new();
1970        for (size, scale) in [(16.0, 4.0), (32.0, 2.0)] {
1971            let span = Span {
1972                text: "a".into(),
1973                face: Face {
1974                    family: "DM Sans".into(),
1975                    size,
1976                    line: size * 1.2,
1977                    weight: 500,
1978                    italic: false,
1979                    spacing: 0.0,
1980                },
1981                color: [1.0; 4],
1982            };
1983            let block = engine
1984                .layout_spans(std::slice::from_ref(&span), None, scale, Anchor::Start)
1985                .unwrap();
1986            let glyph = &block.buffer.layout_runs().next().unwrap().glyphs[0];
1987            let font_id = glyph.font_id;
1988            let glyph_id = glyph.glyph_id;
1989            let font_weight = glyph.font_weight;
1990            let flags = glyph.cache_key_flags;
1991            let font_size = glyph.font_size;
1992            let font = engine.fonts.get_font(font_id, font_weight).unwrap();
1993            let key = raster_key(
1994                font_id,
1995                glyph_id,
1996                font_size,
1997                font_weight,
1998                font_size / scale,
1999                flags,
2000                font.as_swash(),
2001            );
2002            assert_eq!(key.opsz, Some(size.to_bits()));
2003            let rendered = engine
2004                .render_spans(
2005                    &[span],
2006                    None,
2007                    scale,
2008                    Anchor::Start,
2009                    Representation::Coverage,
2010                    [0.0; 2],
2011                    1.0,
2012                    [0.0; 4],
2013                    [0.0; 3],
2014                )
2015                .unwrap();
2016            let (width, height, pixels) = rendered_pixels(&rendered);
2017            let mut context = ScaleContext::new();
2018            let mut scaler = context
2019                .builder(font.as_swash())
2020                .size(font_size)
2021                .hint(!flags.contains(CacheKeyFlags::DISABLE_HINTING))
2022                .variations(key.variations())
2023                .build();
2024            let reference = Render::new(&[Source::Outline])
2025                .render(&mut scaler, glyph_id)
2026                .unwrap();
2027            assert_eq!(
2028                (width, height),
2029                (reference.placement.width, reference.placement.height)
2030            );
2031            assert_eq!(pixels, reference.data);
2032            keys.push(key);
2033            images.push(pixels);
2034        }
2035        assert_eq!(keys[0].ppem, keys[1].ppem);
2036        assert_ne!(keys[0].opsz, keys[1].opsz);
2037        assert_ne!(images[0], images[1]);
2038    }
2039
2040    #[test]
2041    fn fake_italic_shears_vertical_stem() {
2042        let mut engine = mono_engine();
2043        let mut spans = mono("H");
2044        spans[0].face.size = 64.0;
2045        spans[0].face.line = 72.0;
2046        let upright = engine
2047            .render_spans(
2048                &spans,
2049                None,
2050                1.0,
2051                Anchor::Start,
2052                Representation::Coverage,
2053                [0.0; 2],
2054                1.0,
2055                [0.0; 4],
2056                [0.0; 3],
2057            )
2058            .unwrap();
2059        spans[0].face.italic = true;
2060        let block = engine
2061            .layout_spans(&spans, None, 1.0, Anchor::Start)
2062            .unwrap();
2063        let glyph = &block.buffer.layout_runs().next().unwrap().glyphs[0];
2064        let font_id = glyph.font_id;
2065        let flags = glyph.cache_key_flags;
2066        assert!(
2067            raster_flags(
2068                flags,
2069                true,
2070                engine.fonts.db().face(font_id).map(|face| face.style),
2071            )
2072            .contains(CacheKeyFlags::FAKE_ITALIC)
2073        );
2074        let italic = engine
2075            .render_spans(
2076                &spans,
2077                None,
2078                1.0,
2079                Anchor::Start,
2080                Representation::Coverage,
2081                [0.0; 2],
2082                1.0,
2083                [0.0; 4],
2084                [0.0; 3],
2085            )
2086            .unwrap();
2087        let (width, height, pixels) = rendered_pixels(&italic);
2088        let left_at = |row: u32| {
2089            let x = (0..width)
2090                .find(|&x| pixels[(row * width + x) as usize] >= 128)
2091                .unwrap();
2092            italic.quads[0].rect[0] + x as f32
2093        };
2094        let top = height / 4;
2095        let bottom = height * 3 / 4;
2096        let shift = left_at(top) - left_at(bottom);
2097        let expected = (bottom - top) as f32 * 14.0f32.to_radians().tan();
2098        assert!(
2099            (shift - expected).abs() < 2.0,
2100            "shift {shift}, expected {expected}"
2101        );
2102        assert!(italic.quads[0].rect[2] > upright.quads[0].rect[2]);
2103        spans[0].face.italic = false;
2104        let upright_msdf = engine
2105            .render_spans(
2106                &spans,
2107                None,
2108                1.0,
2109                Anchor::Start,
2110                Representation::Msdf,
2111                [0.0; 2],
2112                1.0,
2113                [0.0; 4],
2114                [0.0; 3],
2115            )
2116            .unwrap();
2117        spans[0].face.italic = true;
2118        let italic_msdf = engine
2119            .render_spans(
2120                &spans,
2121                None,
2122                1.0,
2123                Anchor::Start,
2124                Representation::Msdf,
2125                [0.0; 2],
2126                1.0,
2127                [0.0; 4],
2128                [0.0; 3],
2129            )
2130            .unwrap();
2131        assert!(italic_msdf.quads[0].rect[2] > upright_msdf.quads[0].rect[2]);
2132    }
2133
2134    #[test]
2135    fn raster_keys_distinguish_weight_coordinates_and_italic() {
2136        let mut engine = TextEngine::new(VARIABLE_FONT).unwrap();
2137        let font_id = engine.fonts.db().faces().next().unwrap().id;
2138        let font = engine.fonts.get_font(font_id, fontdb::Weight(400)).unwrap();
2139        let regular = raster_key(
2140            font_id,
2141            42,
2142            64.0,
2143            fontdb::Weight(400),
2144            16.0,
2145            CacheKeyFlags::empty(),
2146            font.as_swash(),
2147        );
2148        let heavy = raster_key(
2149            font_id,
2150            42,
2151            64.0,
2152            fontdb::Weight(700),
2153            16.0,
2154            CacheKeyFlags::empty(),
2155            font.as_swash(),
2156        );
2157        let italic = raster_key(
2158            font_id,
2159            42,
2160            64.0,
2161            fontdb::Weight(400),
2162            16.0,
2163            CacheKeyFlags::FAKE_ITALIC,
2164            font.as_swash(),
2165        );
2166        assert_ne!(regular, heavy);
2167        assert_ne!(regular, italic);
2168        assert_eq!(regular.cell_key(), regular.clone().cell_key());
2169        assert_ne!(regular.cell_key(), heavy.cell_key());
2170        assert_ne!(regular.cell_key(), italic.cell_key());
2171        assert_ne!(
2172            regular.cell_key(),
2173            RasterKey {
2174                ppem: 65.0f32.to_bits(),
2175                ..regular.clone()
2176            }
2177            .cell_key()
2178        );
2179        assert_ne!(
2180            regular.cell_key(),
2181            RasterKey {
2182                x_bin: SubpixelBin::One,
2183                ..regular.clone()
2184            }
2185            .cell_key()
2186        );
2187        let optical = RasterKey {
2188            opsz: Some(20.0f32.to_bits()),
2189            ..regular.clone()
2190        };
2191        assert_ne!(regular, optical);
2192        assert_ne!(regular.cell_key(), optical.cell_key());
2193        assert_ne!(
2194            optical,
2195            RasterKey {
2196                opsz: Some(30.0f32.to_bits()),
2197                ..optical.clone()
2198            }
2199        );
2200    }
2201
2202    fn mono_face() -> Face {
2203        Face {
2204            family: "IBM Plex Mono".into(),
2205            size: 20.0,
2206            line: 24.0,
2207            weight: 400,
2208            italic: false,
2209            spacing: 0.0,
2210        }
2211    }
2212
2213    fn mono_engine() -> TextEngine {
2214        TextEngine::new(MONO_FONT).unwrap()
2215    }
2216
2217    fn mono(text: &str) -> Vec<Span> {
2218        vec![Span {
2219            text: text.into(),
2220            face: mono_face(),
2221            color: [0.2, 0.4, 0.6, 1.0],
2222        }]
2223    }
2224
2225    #[test]
2226    #[ignore = "manual timing"]
2227    fn measure_rich_translation() {
2228        let mut engine = mono_engine();
2229        for (name, text) in [
2230            ("label", "Cache"),
2231            (
2232                "paragraph",
2233                "A paragraph of text that moves across the page. This measures repeated raster work and placement across multiple lines of text.",
2234            ),
2235        ] {
2236            let spans = mono(text);
2237            let width = (name == "paragraph").then_some(220.0);
2238            let render = |engine: &mut TextEngine, x| {
2239                engine
2240                    .render_spans(
2241                        &spans,
2242                        width,
2243                        1.0,
2244                        Anchor::Start,
2245                        Representation::Coverage,
2246                        [x, 0.0],
2247                        1.0,
2248                        [0.0; 4],
2249                        [0.0; 3],
2250                    )
2251                    .unwrap()
2252            };
2253            for x in [0.0, 0.25] {
2254                let _ = render(&mut engine, x);
2255            }
2256            let start = std::time::Instant::now();
2257            for i in 0..100 {
2258                let _ = render(&mut engine, (i % 2) as f32 * 0.25);
2259            }
2260            println!(
2261                "{name}: {:.4} ms/call",
2262                start.elapsed().as_secs_f64() * 10.0
2263            );
2264        }
2265    }
2266
2267    #[test]
2268    fn quarter_pixel_bins_shift_coverage() {
2269        let mut engine = mono_engine();
2270        let mut spans = mono("H");
2271        spans[0].face.size = 64.0;
2272        spans[0].face.line = 72.0;
2273        let mut keys = Vec::new();
2274        let mut centroids = Vec::new();
2275        for offset in [0.0, 0.25, 0.5, 0.75] {
2276            let rendered = engine
2277                .render_spans(
2278                    &spans,
2279                    None,
2280                    1.0,
2281                    Anchor::Start,
2282                    Representation::Coverage,
2283                    [offset, 0.0],
2284                    1.0,
2285                    [0.0; 4],
2286                    [0.0; 3],
2287                )
2288                .unwrap();
2289            let quad = &rendered.quads[0];
2290            assert_eq!(quad.atlas_page, AtlasPage::Monochrome);
2291            assert!(!quad.is_color);
2292            assert_eq!(rendered.cells[&quad.key].page, AtlasPage::Monochrome);
2293            keys.push(quad.key);
2294            let (width, height, pixels) = rendered_pixels(&rendered);
2295            let mut sum = 0.0;
2296            let mut mass = 0.0;
2297            for y in 0..height {
2298                for x in 0..width {
2299                    let ink = pixels[(y * width + x) as usize] as f32;
2300                    sum += (quad.rect[0] + x as f32 + 0.5) * ink;
2301                    mass += ink;
2302                }
2303            }
2304            centroids.push(sum / mass);
2305        }
2306        assert_eq!(
2307            keys.iter().collect::<std::collections::BTreeSet<_>>().len(),
2308            4
2309        );
2310        for pair in centroids.windows(2) {
2311            assert!((pair[1] - pair[0] - 0.25).abs() < 0.08, "{centroids:?}");
2312        }
2313        let mut y_keys = Vec::new();
2314        let mut y_centroids = Vec::new();
2315        for offset in [0.0, 0.25, 0.5, 0.75] {
2316            let rendered = engine
2317                .render_spans(
2318                    &spans,
2319                    None,
2320                    1.0,
2321                    Anchor::Start,
2322                    Representation::Coverage,
2323                    [0.0, offset],
2324                    1.0,
2325                    [0.0; 4],
2326                    [0.0; 3],
2327                )
2328                .unwrap();
2329            let quad = &rendered.quads[0];
2330            y_keys.push(quad.key);
2331            let (width, height, pixels) = rendered_pixels(&rendered);
2332            let mut sum = 0.0;
2333            let mut mass = 0.0;
2334            for y in 0..height {
2335                for x in 0..width {
2336                    let ink = pixels[(y * width + x) as usize] as f32;
2337                    sum += (quad.rect[1] + y as f32 + 0.5) * ink;
2338                    mass += ink;
2339                }
2340            }
2341            y_centroids.push(sum / mass);
2342        }
2343        assert_eq!(
2344            y_keys
2345                .iter()
2346                .collect::<std::collections::BTreeSet<_>>()
2347                .len(),
2348            1
2349        );
2350        for pair in y_centroids.windows(2) {
2351            assert!((pair[1] - pair[0]).abs() < 0.01, "{y_centroids:?}");
2352        }
2353        let repeated = engine
2354            .render_spans(
2355                &spans,
2356                None,
2357                1.0,
2358                Anchor::Start,
2359                Representation::Coverage,
2360                [0.0; 2],
2361                1.0,
2362                [0.0; 4],
2363                [0.0; 3],
2364            )
2365            .unwrap();
2366        assert_eq!(repeated.quads[0].key, keys[0]);
2367        let fresh = mono_engine()
2368            .render_spans(
2369                &spans,
2370                None,
2371                1.0,
2372                Anchor::Start,
2373                Representation::Coverage,
2374                [0.0; 2],
2375                1.0,
2376                [0.0; 4],
2377                [0.0; 3],
2378            )
2379            .unwrap();
2380        assert_eq!(fresh.quads[0].key, keys[0]);
2381    }
2382
2383    #[test]
2384    fn y_placement_matches_cosmic_truncation_and_exposes_pens() {
2385        let mut engine = mono_engine();
2386        let mut spans = mono("Hi\nHi");
2387        spans[0].face.line = 24.6;
2388        let scale = 1.25;
2389        for origin in [[0.25, 0.37], [2.5, -0.42], [-1.25, 1.15]] {
2390            let expected = engine
2391                .layout_spans(&spans, None, scale, Anchor::Start)
2392                .unwrap()
2393                .buffer
2394                .layout_runs()
2395                .flat_map(|run| {
2396                    run.glyphs.iter().map(move |glyph| {
2397                        (
2398                            run.line_i,
2399                            run.line_y,
2400                            glyph.x + glyph.x_offset * glyph.font_size,
2401                            glyph.y - glyph.y_offset * glyph.font_size,
2402                        )
2403                    })
2404                })
2405                .collect::<Vec<_>>();
2406            let rendered = engine
2407                .render_spans(
2408                    &spans,
2409                    None,
2410                    scale,
2411                    Anchor::Start,
2412                    Representation::Coverage,
2413                    origin,
2414                    1.0,
2415                    [0.0; 4],
2416                    [0.0; 3],
2417                )
2418                .unwrap();
2419            assert_eq!(rendered.quads.len(), expected.len());
2420            for (quad, (line_index, line_y, glyph_x, glyph_y)) in
2421                rendered.quads.iter().zip(expected)
2422            {
2423                let raster = engine
2424                    .rasters
2425                    .iter()
2426                    .find(|(key, _)| key.cell_key() == quad.key)
2427                    .unwrap()
2428                    .1;
2429                let image = raster.mono.as_ref().unwrap();
2430                let y = (glyph_y + origin[1] * scale + line_y).trunc();
2431                assert!((quad.rect[1] - (y + image.top) / scale).abs() < 0.00001);
2432                assert_eq!(quad.line_index, line_index);
2433                assert!((quad.line_baseline - (line_y / scale + origin[1])).abs() < 0.001);
2434                assert!((quad.pen[0] - (glyph_x / scale + origin[0])).abs() < 0.001);
2435                assert!((quad.pen[1] - ((glyph_y + line_y) / scale + origin[1])).abs() < 0.001);
2436            }
2437        }
2438    }
2439
2440    #[test]
2441    fn raster_cache_reuses_translation_and_rasterizes_new_glyphs_and_sizes() {
2442        let mut engine = mono_engine();
2443        let spans = mono("HH");
2444        let render = |engine: &mut TextEngine, spans: &[Span], origin| {
2445            engine
2446                .render_spans(
2447                    spans,
2448                    None,
2449                    1.0,
2450                    Anchor::Start,
2451                    Representation::Coverage,
2452                    origin,
2453                    1.0,
2454                    [0.0; 4],
2455                    [0.0; 3],
2456                )
2457                .unwrap()
2458        };
2459        let first = render(&mut engine, &spans, [0.0, 0.0]);
2460        let count = engine.cached_rasters();
2461        let translated = render(&mut engine, &spans, [10.0, 0.75]);
2462        assert_eq!(engine.cached_rasters(), count);
2463        assert_eq!(first.quads[0].key, translated.quads[0].key);
2464        assert_eq!(first.atlas.levels[0], translated.atlas.levels[0]);
2465        assert_eq!(translated.quads[0].rect[0] - first.quads[0].rect[0], 10.0);
2466        let _ = render(&mut engine, &mono("HZ"), [0.0, 0.0]);
2467        assert_eq!(engine.cached_rasters(), count + 1);
2468        let mut larger = mono("H");
2469        larger[0].face.size = 24.0;
2470        let _ = render(&mut engine, &larger, [0.0, 0.0]);
2471        assert_eq!(engine.cached_rasters(), count + 2);
2472    }
2473
2474    #[test]
2475    fn color_outline_uses_rgba_page() {
2476        let mut engine = TextEngine::new(COLOR_FONT).unwrap();
2477        let rendered = engine
2478            .render_spans(
2479                &[Span {
2480                    text: "A".into(),
2481                    face: Face {
2482                        family: "Bungee Color".into(),
2483                        size: 64.0,
2484                        line: 72.0,
2485                        weight: 400,
2486                        italic: false,
2487                        spacing: 0.0,
2488                    },
2489                    color: [1.0; 4],
2490                }],
2491                None,
2492                1.0,
2493                Anchor::Start,
2494                Representation::Coverage,
2495                [0.0; 2],
2496                1.0,
2497                [0.0; 4],
2498                [0.0; 3],
2499            )
2500            .unwrap();
2501        let quad = &rendered.quads[0];
2502        assert!(quad.is_color);
2503        assert_eq!(quad.atlas_page, AtlasPage::Color);
2504        let cell = rendered.cells[&quad.key];
2505        assert_eq!(cell.page, AtlasPage::Color);
2506        let atlas = rendered.color_atlas.as_ref().unwrap();
2507        assert_eq!(atlas.channels, 4);
2508        let level = &atlas.levels[0];
2509        let pixels = (cell.rect[1]..cell.rect[1] + cell.rect[3])
2510            .flat_map(|y| {
2511                (cell.rect[0]..cell.rect[0] + cell.rect[2]).map(move |x| {
2512                    let index = ((y * level.width + x) * 4) as usize;
2513                    &level.bytes[index..index + 4]
2514                })
2515            })
2516            .collect::<Vec<_>>();
2517        assert!(pixels.iter().any(|pixel| pixel[3] > 0));
2518        assert!(pixels.iter().any(|pixel| pixel[0] != pixel[1]));
2519    }
2520
2521    #[test]
2522    fn atlas_constants_are_exported() {
2523        assert_eq!(ATLAS_PADDING, 8);
2524        assert_eq!(ATLAS_CELL_ALIGN, 8);
2525    }
2526
2527    #[test]
2528    fn rich_cursor_and_marks_use_byte_offsets() {
2529        let mut engine = mono_engine();
2530        let block = engine
2531            .layout_spans(&mono("iiii"), None, 1.0, Anchor::Start)
2532            .unwrap();
2533        let cell = block.width / 4.0;
2534        let mark = marks(block, 1, 3);
2535        assert_eq!(mark.len(), 1);
2536        assert!((mark[0][0] - cell).abs() < 0.01);
2537        assert!((mark[0][2] - cell * 2.0).abs() < 0.01);
2538        assert_eq!(hit(block, cell * 0.25, block.height * 0.5), Some(0));
2539        assert_eq!(hit(block, cell * 0.75, block.height * 0.5), Some(1));
2540        assert_eq!(hit(block, cell * 2.75, block.height * 0.5), Some(3));
2541        assert_eq!(cursor_at(block, 3), (0, 3));
2542        assert!((baseline(block) - block.baseline).abs() < 0.001);
2543
2544        let block = engine
2545            .layout_spans(&mono("i\ni"), None, 1.0, Anchor::Start)
2546            .unwrap();
2547        assert_eq!(cursor_at(block, 0), (0, 0));
2548        assert_eq!(cursor_at(block, 1), (0, 1));
2549        assert_eq!(cursor_at(block, 2), (1, 0));
2550        assert_eq!(cursor_at(block, 3), (1, 1));
2551        assert_eq!(hit(block, 0.0, block.height * 0.75), Some(2));
2552        assert_eq!(marks(block, 0, 3).len(), 2);
2553    }
2554
2555    #[test]
2556    fn rich_faces_wrap_and_align_together() {
2557        let mut engine = mono_engine();
2558        engine.register_font(FONT).unwrap();
2559        let spans = vec![
2560            Span {
2561                text: "ii".into(),
2562                face: mono_face(),
2563                color: [1.0, 0.0, 0.0, 1.0],
2564            },
2565            Span {
2566                text: "AV".into(),
2567                face: Face {
2568                    family: "EB Garamond".into(),
2569                    size: 24.0,
2570                    line: 28.0,
2571                    weight: 600,
2572                    italic: true,
2573                    spacing: 0.02,
2574                },
2575                color: [0.0, 0.0, 1.0, 0.5],
2576            },
2577        ];
2578        let block = engine
2579            .layout_spans(&spans, None, 1.0, Anchor::Start)
2580            .unwrap();
2581        assert_eq!(cursor_at(block, 4), (0, 4));
2582        let rendered = engine
2583            .render_spans(
2584                &spans,
2585                None,
2586                1.0,
2587                Anchor::Start,
2588                Representation::Msdf,
2589                [5.0, 7.0],
2590                0.5,
2591                [0.0, 0.0, 100.0, 100.0],
2592                [0.0, 0.0, 0.0],
2593            )
2594            .unwrap();
2595        assert_eq!(rendered.quads.len(), 4);
2596        assert_eq!(rendered.quads[0].color, [1.0, 0.0, 0.0, 0.5]);
2597        assert_eq!(rendered.quads[3].color, [0.0, 0.0, 1.0, 0.25]);
2598        assert_eq!(rendered.quads[0].origin, [5.0, 7.0]);
2599
2600        let plain = mono("iiii");
2601        let width = engine
2602            .layout_spans(&plain, None, 1.0, Anchor::Start)
2603            .unwrap()
2604            .width;
2605        let wrapped = engine
2606            .layout_spans(&plain, Some(width * 0.55), 1.0, Anchor::Start)
2607            .unwrap();
2608        assert!(wrapped.height > wrapped.baseline);
2609        assert_eq!(wrapped.buffer.layout_runs().count(), 2);
2610        let container = width + 40.0;
2611        let mut xs = Vec::new();
2612        for anchor in [Anchor::Start, Anchor::Center, Anchor::End] {
2613            let block = engine
2614                .layout_spans(&plain, Some(container), 1.0, anchor)
2615                .unwrap();
2616            xs.push(marks(block, 0, 1)[0][0]);
2617        }
2618        assert!((xs[0] - 0.0).abs() < 0.01);
2619        assert!((xs[1] - 20.0).abs() < 0.01);
2620        assert!((xs[2] - 40.0).abs() < 0.01);
2621    }
2622
2623    #[test]
2624    fn rich_cache_expires_after_an_unused_frame() {
2625        let mut engine = mono_engine();
2626        engine.set_shaping_budget(0);
2627        let spans = mono("iiii");
2628        let first = engine
2629            .layout_spans(&spans, None, 1.0, Anchor::Start)
2630            .unwrap() as *const Block;
2631        let second = engine
2632            .layout_spans(&spans, None, 1.0, Anchor::Start)
2633            .unwrap() as *const Block;
2634        assert_eq!(first, second);
2635        assert_eq!(engine.cached_blocks(), 1);
2636        engine.end_frame();
2637        assert_eq!(engine.cached_blocks(), 1);
2638        engine.begin_frame();
2639        engine.end_frame();
2640        assert_eq!(engine.cached_blocks(), 0);
2641    }
2642
2643    #[test]
2644    fn atlas_padding_is_isolated_at_every_mip() {
2645        let images = BTreeMap::from([
2646            (
2647                0,
2648                Some(GlyphImage {
2649                    left: 0.0,
2650                    top: 0.0,
2651                    width: 16,
2652                    height: 16,
2653                    bytes: vec![30; 256],
2654                }),
2655            ),
2656            (
2657                1,
2658                Some(GlyphImage {
2659                    left: 0.0,
2660                    top: 0.0,
2661                    width: 16,
2662                    height: 16,
2663                    bytes: vec![220; 256],
2664                }),
2665            ),
2666        ]);
2667        let (atlas, rects) = pack(&images, 1, Some(4)).unwrap();
2668        assert_eq!(atlas.levels.len(), 4);
2669        for (level_index, level) in atlas.levels.iter().enumerate() {
2670            for (key, value) in [(0, 30u8), (1, 220u8)] {
2671                let rect = rects[&key];
2672                let shift = level_index as u32;
2673                let y = (rect[1] >> shift) + 1;
2674                for x in ((rect[0] - 8) >> shift)..=((rect[0] + rect[2] + 7) >> shift) {
2675                    assert_eq!(level.bytes[(y * level.width + x) as usize], value);
2676                }
2677            }
2678        }
2679    }
2680
2681    fn style(representation: Representation, density: f32) -> Style<'static> {
2682        Style {
2683            family: "EB Garamond",
2684            size: 1.0,
2685            line_height: 1.2,
2686            wrap_width: None,
2687            pixels_per_unit: density,
2688            representation,
2689        }
2690    }
2691
2692    fn straight() -> Baseline {
2693        Baseline::Straight {
2694            origin: [0.0, 0.0],
2695            direction: [1.0, 0.0],
2696        }
2697    }
2698
2699    #[test]
2700    fn atlas_bytes_are_deterministic() {
2701        let mut a = TextEngine::new(FONT).unwrap();
2702        let mut b = TextEngine::new(FONT).unwrap();
2703        for representation in [Representation::Coverage, Representation::Msdf] {
2704            let first = a
2705                .layout("AVATAR ffi été", style(representation, 96.0), &straight())
2706                .unwrap();
2707            let second = b
2708                .layout("AVATAR ffi été", style(representation, 96.0), &straight())
2709                .unwrap();
2710            assert_eq!(first.atlas.channels, second.atlas.channels);
2711            assert_eq!(first.atlas.levels, second.atlas.levels);
2712            assert_eq!(first.glyphs.len(), second.glyphs.len());
2713            assert!(first.atlas.levels.len() > 1);
2714        }
2715    }
2716
2717    #[test]
2718    fn coverage_matches_reference_glyph() {
2719        let mut engine = TextEngine::new(FONT).unwrap();
2720        let paragraph = engine
2721            .layout("A", style(Representation::Coverage, 64.0), &straight())
2722            .unwrap();
2723        let glyph = &paragraph.glyphs[0];
2724        let rect = glyph.atlas_rect;
2725        let atlas = &paragraph.atlas.levels[0];
2726        let mut pixels = Vec::new();
2727        for y in rect[1]..rect[1] + rect[3] {
2728            let start = (y * atlas.width + rect[0]) as usize;
2729            pixels.extend_from_slice(&atlas.bytes[start..start + rect[2] as usize]);
2730        }
2731        let checksum = pixels.iter().fold(0xcbf29ce484222325u64, |hash, value| {
2732            (hash ^ u64::from(*value)).wrapping_mul(0x100000001b3)
2733        });
2734        assert_eq!((rect[2], rect[3], checksum), (46, 45, 5879817070010596299));
2735    }
2736
2737    #[test]
2738    fn msdf_reconstructs_coverage_at_multiple_scales() {
2739        let mut engine = TextEngine::new(FONT).unwrap();
2740        let msdf = engine
2741            .layout("A", style(Representation::Msdf, 64.0), &straight())
2742            .unwrap();
2743        let m = &msdf.glyphs[0];
2744        for density in [32.0, 64.0, 128.0] {
2745            let coverage = engine
2746                .layout("A", style(Representation::Coverage, density), &straight())
2747                .unwrap();
2748            let c = &coverage.glyphs[0];
2749            let level = &coverage.atlas.levels[0];
2750            let mut error = 0.0;
2751            let mut count = 0;
2752            for y in 0..c.atlas_rect[3] {
2753                for x in 0..c.atlas_rect[2] {
2754                    let px = (x as f32 + c.local_rect[0] + 0.5) / density;
2755                    let py = (y as f32 + c.local_rect[1] + 0.5) / density;
2756                    let sx = px * 64.0 - m.local_rect[0];
2757                    let sy = py * 64.0 - m.local_rect[1];
2758                    let rgb = sample_rgb(&msdf.atlas.levels[0], m.atlas_rect, sx, sy);
2759                    let predicted = msdf_coverage(rgb, 4.0 * density / 64.0);
2760                    let reference = level.bytes
2761                        [((c.atlas_rect[1] + y) * level.width + c.atlas_rect[0] + x) as usize]
2762                        as f32
2763                        / 255.0;
2764                    error += (predicted - reference).abs();
2765                    count += 1;
2766                }
2767            }
2768            assert!(
2769                error / (count as f32) < 0.17,
2770                "density {density}: {}",
2771                error / count as f32
2772            );
2773        }
2774    }
2775
2776    fn sample_rgb(level: &MipLevel, rect: [u32; 4], x: f32, y: f32) -> [f32; 3] {
2777        let x = x - 0.5;
2778        let y = y - 0.5;
2779        let x0 = x.floor();
2780        let y0 = y.floor();
2781        let mut rgb = [0.0; 3];
2782        for dy in 0..2 {
2783            for dx in 0..2 {
2784                let sx = (x0 as i32 + dx).clamp(0, rect[2] as i32 - 1) as u32;
2785                let sy = (y0 as i32 + dy).clamp(0, rect[3] as i32 - 1) as u32;
2786                let wx = if dx == 0 { 1.0 - x.fract() } else { x.fract() };
2787                let wy = if dy == 0 { 1.0 - y.fract() } else { y.fract() };
2788                let at = (((rect[1] + sy) * level.width + rect[0] + sx) * 3) as usize;
2789                for (channel, value) in rgb.iter_mut().enumerate() {
2790                    *value += level.bytes[at + channel] as f32 / 255.0 * wx * wy;
2791                }
2792            }
2793        }
2794        rgb
2795    }
2796
2797    #[test]
2798    fn glyphs_follow_curved_baselines() {
2799        let mut engine = TextEngine::new(FONT).unwrap();
2800        let style = style(Representation::Msdf, 64.0);
2801        let flat = engine.layout("CURVE", style, &straight()).unwrap();
2802        let path = Baseline::Arc {
2803            center: [2.0, 4.0],
2804            radius: 8.0,
2805            start_angle: -std::f32::consts::FRAC_PI_2,
2806            clockwise: false,
2807        };
2808        let curved = engine.layout("CURVE", style, &path).unwrap();
2809        for (a, b) in flat.glyphs.iter().zip(&curved.glyphs) {
2810            let (expected, angle) = path.sample(a.position[0], 0.0).unwrap();
2811            assert!((b.position[0] - expected[0]).abs() < 0.001);
2812            assert!((b.position[1] - expected[1]).abs() < 0.001);
2813            assert!((b.rotation - angle).abs() < 0.001);
2814        }
2815        let polyline = Baseline::Polyline(vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]);
2816        let first = polyline.sample(0.5, 0.0).unwrap();
2817        let second = polyline.sample(1.5, 0.0).unwrap();
2818        assert_eq!(first.0, [0.5, 0.0]);
2819        assert_eq!(second.0, [1.0, 0.5]);
2820        assert!((second.1 - std::f32::consts::FRAC_PI_2).abs() < 0.001);
2821    }
2822}