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 = ¶graph.quads[0];
1865 let level = ¶graph.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 = ¶graph.glyphs[0];
2724 let rect = glyph.atlas_rect;
2725 let atlas = ¶graph.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}