ratatui_wgpu/backend/
wgpu_backend.rs

1use std::{
2    collections::{
3        HashMap,
4        HashSet,
5    },
6    marker::PhantomData,
7    mem::size_of,
8    num::NonZeroU64,
9};
10
11use bitvec::{
12    order::Lsb0,
13    slice::BitSlice,
14    vec::BitVec,
15};
16use indexmap::IndexMap;
17use raqote::{
18    DrawOptions,
19    DrawTarget,
20    SolidSource,
21    StrokeStyle,
22    Transform,
23};
24use ratatui::{
25    backend::{
26        Backend,
27        ClearType,
28        WindowSize,
29    },
30    buffer::Cell,
31    layout::{
32        Position,
33        Size,
34    },
35    style::Modifier,
36};
37use rustybuzz::{
38    shape_with_plan,
39    ttf_parser::{
40        GlyphId,
41        RasterGlyphImage,
42        RasterImageFormat,
43        RgbaColor,
44    },
45    GlyphBuffer,
46    UnicodeBuffer,
47};
48use unicode_bidi::{
49    Level,
50    ParagraphBidiInfo,
51};
52use unicode_properties::{
53    GeneralCategoryGroup,
54    UnicodeEmoji,
55    UnicodeGeneralCategory,
56};
57use unicode_width::{
58    UnicodeWidthChar,
59    UnicodeWidthStr,
60};
61use web_time::{
62    Duration,
63    Instant,
64};
65use wgpu::{
66    util::{
67        BufferInitDescriptor,
68        DeviceExt,
69    },
70    Buffer,
71    BufferUsages,
72    CommandEncoderDescriptor,
73    Device,
74    Extent3d,
75    IndexFormat,
76    LoadOp,
77    Operations,
78    Origin3d,
79    Queue,
80    RenderPassColorAttachment,
81    RenderPassDescriptor,
82    StoreOp,
83    Surface,
84    SurfaceConfiguration,
85    Texture,
86    TextureAspect,
87};
88
89use crate::{
90    backend::{
91        build_wgpu_state,
92        c2c,
93        private::Token,
94        PostProcessor,
95        RenderSurface,
96        RenderTexture,
97        TextBgVertexMember,
98        TextCacheBgPipeline,
99        TextCacheFgPipeline,
100        TextVertexMember,
101        Viewport,
102        WgpuState,
103    },
104    colors::Rgb,
105    fonts::{
106        Font,
107        Fonts,
108    },
109    shaders::DefaultPostProcessor,
110    utils::{
111        plan_cache::PlanCache,
112        text_atlas::{
113            Atlas,
114            CacheRect,
115            Entry,
116            Key,
117        },
118        Outline,
119        Painter,
120    },
121    RandomState,
122};
123
124const NULL_CELL: Cell = Cell::new("");
125
126pub(super) struct RenderInfo {
127    cell: usize,
128    cached: CacheRect,
129    underline_pos_min: u16,
130    underline_pos_max: u16,
131}
132/// Map from (x, y, glyph) -> (cell index, cache entry).
133/// We use an IndexMap because we want a consistent rendering order for
134/// vertices.
135type Rendered = IndexMap<(i32, i32, GlyphId), RenderInfo, RandomState>;
136
137/// Set of (x, y, glyph, char width).
138type Sourced = HashSet<(i32, i32, GlyphId, u32), RandomState>;
139
140/// A ratatui backend leveraging wgpu for rendering.
141///
142/// Constructed using a [`Builder`](crate::Builder).
143///
144/// The first lifetime parameter is the lifetime of the data for referenced
145/// [`Font`] objects. The second lifetime parameter is the lifetime of the
146/// referenced [`Surface`] (typically the lifetime of your window object).
147///
148/// Limitations:
149/// - The cursor is tracked but not rendered.
150/// - No builtin accessibilty, although [`WgpuBackend::get_text`] is provided to
151///   access the screen's contents.
152pub struct WgpuBackend<
153    'f,
154    's,
155    P: PostProcessor = DefaultPostProcessor,
156    S: RenderSurface<'s> = Surface<'s>,
157> {
158    pub(super) post_process: P,
159
160    pub(super) cells: Vec<Cell>,
161    pub(super) dirty_rows: Vec<bool>,
162    pub(super) dirty_cells: BitVec,
163    pub(super) rendered: Vec<Rendered>,
164    pub(super) sourced: Vec<Sourced>,
165    pub(super) fast_blinking: BitVec,
166    pub(super) slow_blinking: BitVec,
167
168    pub(super) cursor: (u16, u16),
169
170    pub(super) viewport: Viewport,
171
172    pub(super) surface: S,
173    pub(super) _surface: PhantomData<&'s S>,
174    pub(super) surface_config: SurfaceConfiguration,
175    pub(super) device: Device,
176    pub(super) queue: Queue,
177
178    pub(super) plan_cache: PlanCache,
179    pub(super) buffer: UnicodeBuffer,
180    pub(super) row: String,
181    pub(super) rowmap: Vec<u16>,
182
183    pub(super) cached: Atlas,
184    pub(super) text_cache: Texture,
185    pub(super) text_mask: Texture,
186    pub(super) bg_vertices: Vec<TextBgVertexMember>,
187    pub(super) text_indices: Vec<[u32; 6]>,
188    pub(super) text_vertices: Vec<TextVertexMember>,
189    pub(super) text_bg_compositor: TextCacheBgPipeline,
190    pub(super) text_fg_compositor: TextCacheFgPipeline,
191    pub(super) text_screen_size_buffer: Buffer,
192
193    pub(super) wgpu_state: WgpuState,
194
195    pub(super) fonts: Fonts<'f>,
196    pub(super) reset_fg: Rgb,
197    pub(super) reset_bg: Rgb,
198
199    pub(super) fast_duration: Duration,
200    pub(super) last_fast_toggle: Instant,
201    pub(super) show_fast: bool,
202    pub(super) slow_duration: Duration,
203    pub(super) last_slow_toggle: Instant,
204    pub(super) show_slow: bool,
205}
206
207impl<'f, 's, P: PostProcessor, S: RenderSurface<'s>> WgpuBackend<'f, 's, P, S> {
208    /// Get the [`PostProcessor`] associated with this backend.
209    pub fn post_processor(&self) -> &P {
210        &self.post_process
211    }
212
213    /// Get a mutable reference to the [`PostProcessor`] associated with this
214    /// backend.
215    pub fn post_processor_mut(&mut self) -> &mut P {
216        &mut self.post_process
217    }
218
219    /// Resize the rendering surface. This should be called e.g. to keep the
220    /// backend in sync with your window size.
221    pub fn resize(&mut self, width: u32, height: u32) {
222        let limits = self.device.limits();
223        let width = width.min(limits.max_texture_dimension_2d);
224        let height = height.min(limits.max_texture_dimension_2d);
225
226        if width == self.surface_config.width && height == self.surface_config.height
227            || width == 0
228            || height == 0
229        {
230            return;
231        }
232
233        let (inset_width, inset_height) = match self.viewport {
234            Viewport::Full => (0, 0),
235            Viewport::Shrink { width, height } => (width, height),
236        };
237
238        let dims = self.size().unwrap();
239        let current_width = dims.width;
240        let current_height = dims.height;
241
242        self.surface_config.width = width;
243        self.surface_config.height = height;
244        self.surface
245            .configure(&self.device, &self.surface_config, Token);
246
247        let width = width - inset_width;
248        let height = height - inset_height;
249
250        let chars_wide = width / self.fonts.min_width_px();
251        let chars_high = height / self.fonts.height_px();
252
253        if chars_wide != current_width as u32 || chars_high != current_height as u32 {
254            self.cells.clear();
255            self.rendered.clear();
256            self.sourced.clear();
257            self.fast_blinking.clear();
258            self.slow_blinking.clear();
259        }
260
261        // This always needs to be cleared because the surface is cleared when it is
262        // resized. If we don't re-render the rows, we end up with a blank surface when
263        // the resize is less than a character dimension.
264        self.dirty_rows.clear();
265
266        self.wgpu_state = build_wgpu_state(
267            &self.device,
268            chars_wide * self.fonts.min_width_px(),
269            chars_high * self.fonts.height_px(),
270        );
271
272        self.post_process.resize(
273            &self.device,
274            &self.wgpu_state.text_dest_view,
275            &self.surface_config,
276        );
277
278        info!(
279            "Resized from {}x{} to {}x{}",
280            current_width, current_height, chars_wide, chars_high,
281        );
282    }
283
284    /// Get the text currently displayed on the screen.
285    pub fn get_text(&self) -> String {
286        let bounds = self.size().unwrap();
287        self.cells.chunks(bounds.width as usize).fold(
288            String::with_capacity((bounds.width + 1) as usize * bounds.height as usize),
289            |dest, row| {
290                let mut dest = row.iter().fold(dest, |mut dest, s| {
291                    dest.push_str(s.symbol());
292                    dest
293                });
294                dest.push('\n');
295                dest
296            },
297        )
298    }
299
300    /// Update the fonts used for rendering. This will cause a full repaint of
301    /// the screen the next time [`WgpuBackend::flush`] is called.
302    pub fn update_fonts(&mut self, new_fonts: Fonts<'f>) {
303        self.dirty_rows.clear();
304        self.cached.match_fonts(&new_fonts);
305        self.fonts = new_fonts;
306    }
307
308    fn render(&mut self) {
309        let bounds = self.window_size().unwrap();
310
311        let mut encoder = self
312            .device
313            .create_command_encoder(&CommandEncoderDescriptor {
314                label: Some("Draw Encoder"),
315            });
316
317        if !self.text_vertices.is_empty() {
318            {
319                let mut uniforms = self
320                    .queue
321                    .write_buffer_with(
322                        &self.text_screen_size_buffer,
323                        0,
324                        NonZeroU64::new(size_of::<[f32; 4]>() as u64).unwrap(),
325                    )
326                    .unwrap();
327                uniforms.copy_from_slice(bytemuck::cast_slice(&[
328                    bounds.columns_rows.width as f32 * self.fonts.min_width_px() as f32,
329                    bounds.columns_rows.height as f32 * self.fonts.height_px() as f32,
330                    0.0,
331                    0.0,
332                ]));
333            }
334
335            let bg_vertices = self.device.create_buffer_init(&BufferInitDescriptor {
336                label: Some("Text Bg Vertices"),
337                contents: bytemuck::cast_slice(&self.bg_vertices),
338                usage: BufferUsages::VERTEX,
339            });
340
341            let fg_vertices = self.device.create_buffer_init(&BufferInitDescriptor {
342                label: Some("Text Vertices"),
343                contents: bytemuck::cast_slice(&self.text_vertices),
344                usage: BufferUsages::VERTEX,
345            });
346
347            let indices = self.device.create_buffer_init(&BufferInitDescriptor {
348                label: Some("Text Indices"),
349                contents: bytemuck::cast_slice(&self.text_indices),
350                usage: BufferUsages::INDEX,
351            });
352
353            {
354                let mut text_render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
355                    label: Some("Text Render Pass"),
356                    color_attachments: &[Some(RenderPassColorAttachment {
357                        view: &self.wgpu_state.text_dest_view,
358                        resolve_target: None,
359                        ops: Operations {
360                            load: LoadOp::Load,
361                            store: StoreOp::Store,
362                        },
363                    })],
364                    ..Default::default()
365                });
366
367                text_render_pass.set_index_buffer(indices.slice(..), IndexFormat::Uint32);
368
369                text_render_pass.set_pipeline(&self.text_bg_compositor.pipeline);
370                text_render_pass.set_bind_group(0, &self.text_bg_compositor.fs_uniforms, &[]);
371                text_render_pass.set_vertex_buffer(0, bg_vertices.slice(..));
372                text_render_pass.draw_indexed(0..(self.bg_vertices.len() as u32 / 4) * 6, 0, 0..1);
373
374                text_render_pass.set_pipeline(&self.text_fg_compositor.pipeline);
375                text_render_pass.set_bind_group(0, &self.text_fg_compositor.fs_uniforms, &[]);
376                text_render_pass.set_bind_group(1, &self.text_fg_compositor.atlas_bindings, &[]);
377
378                text_render_pass.set_vertex_buffer(0, fg_vertices.slice(..));
379                text_render_pass.draw_indexed(
380                    0..(self.text_vertices.len() as u32 / 4) * 6,
381                    0,
382                    0..1,
383                );
384            }
385        }
386
387        let Some(texture) = self.surface.get_current_texture(Token) else {
388            return;
389        };
390
391        self.post_process.process(
392            &mut encoder,
393            &self.queue,
394            &self.wgpu_state.text_dest_view,
395            &self.surface_config,
396            texture.get_view(Token),
397        );
398
399        self.queue.submit(Some(encoder.finish()));
400        texture.present(Token);
401    }
402}
403
404impl<'s, P: PostProcessor, S: RenderSurface<'s>> Backend for WgpuBackend<'_, 's, P, S> {
405    fn draw<'a, I>(&mut self, content: I) -> std::io::Result<()>
406    where
407        I: Iterator<Item = (u16, u16, &'a Cell)>,
408    {
409        let bounds = self.size()?;
410
411        self.cells
412            .resize(bounds.height as usize * bounds.width as usize, Cell::EMPTY);
413        self.sourced.resize_with(
414            bounds.height as usize * bounds.width as usize,
415            Sourced::default,
416        );
417        self.rendered.resize_with(
418            bounds.height as usize * bounds.width as usize,
419            Rendered::default,
420        );
421        self.fast_blinking
422            .resize(bounds.height as usize * bounds.width as usize, false);
423        self.slow_blinking
424            .resize(bounds.height as usize * bounds.width as usize, false);
425        self.dirty_rows.resize(bounds.height as usize, true);
426
427        for (x, y, cell) in content {
428            let index = y as usize * bounds.width as usize + x as usize;
429
430            self.fast_blinking
431                .set(index, cell.modifier.contains(Modifier::RAPID_BLINK));
432            self.slow_blinking
433                .set(index, cell.modifier.contains(Modifier::SLOW_BLINK));
434
435            self.cells[index] = cell.clone();
436
437            let width = cell.symbol().width().max(1);
438            let start = (index + 1).min(self.cells.len());
439            let end = (index + width).min(self.cells.len());
440            self.cells[start..end].fill(NULL_CELL);
441            self.dirty_rows[y as usize] = true;
442        }
443
444        Ok(())
445    }
446
447    fn hide_cursor(&mut self) -> std::io::Result<()> {
448        Ok(())
449    }
450
451    fn show_cursor(&mut self) -> std::io::Result<()> {
452        Ok(())
453    }
454
455    fn get_cursor_position(&mut self) -> std::io::Result<Position> {
456        Ok(Position::new(self.cursor.0, self.cursor.1))
457    }
458
459    fn set_cursor_position<Pos: Into<Position>>(&mut self, position: Pos) -> std::io::Result<()> {
460        let bounds = self.size()?;
461        let pos: Position = position.into();
462        self.cursor = (pos.x.min(bounds.width - 1), pos.y.min(bounds.height - 1));
463        Ok(())
464    }
465
466    fn clear(&mut self) -> std::io::Result<()> {
467        self.cells.clear();
468        self.dirty_rows.clear();
469        self.cursor = (0, 0);
470
471        Ok(())
472    }
473
474    fn size(&self) -> std::io::Result<Size> {
475        let (inset_width, inset_height) = match self.viewport {
476            Viewport::Full => (0, 0),
477            Viewport::Shrink { width, height } => (width, height),
478        };
479        let width = self.surface_config.width - inset_width;
480        let height = self.surface_config.height - inset_height;
481
482        Ok(Size {
483            width: (width / self.fonts.min_width_px()) as u16,
484            height: (height / self.fonts.height_px()) as u16,
485        })
486    }
487
488    fn window_size(&mut self) -> std::io::Result<WindowSize> {
489        let (inset_width, inset_height) = match self.viewport {
490            Viewport::Full => (0, 0),
491            Viewport::Shrink { width, height } => (width, height),
492        };
493        let width = self.surface_config.width - inset_width;
494        let height = self.surface_config.height - inset_height;
495
496        Ok(WindowSize {
497            columns_rows: Size {
498                width: (width / self.fonts.min_width_px()) as u16,
499                height: (height / self.fonts.height_px()) as u16,
500            },
501            pixels: Size {
502                width: width as u16,
503                height: height as u16,
504            },
505        })
506    }
507
508    fn flush(&mut self) -> std::io::Result<()> {
509        let bounds = self.size()?;
510        self.dirty_cells.clear();
511        self.dirty_cells.resize(self.cells.len(), false);
512
513        let fast_toggle_dirty = self.last_fast_toggle.elapsed() >= self.fast_duration;
514        if fast_toggle_dirty {
515            self.last_fast_toggle = Instant::now();
516            self.show_fast = !self.show_fast;
517
518            for index in self.fast_blinking.iter_ones() {
519                self.dirty_cells.set(index, true);
520            }
521        }
522
523        let slow_toggle_dirty = self.last_slow_toggle.elapsed() >= self.slow_duration;
524        if slow_toggle_dirty {
525            self.last_slow_toggle = Instant::now();
526            self.show_slow = !self.show_slow;
527
528            for index in self.slow_blinking.iter_ones() {
529                self.dirty_cells.set(index, true);
530            }
531        }
532
533        let mut pending_cache_updates = HashMap::<_, _, RandomState>::default();
534
535        for (y, (row, sourced)) in self
536            .cells
537            .chunks(bounds.width as usize)
538            .zip(self.sourced.chunks_mut(bounds.width as usize))
539            .enumerate()
540        {
541            if !self.dirty_rows[y] {
542                continue;
543            }
544
545            self.dirty_rows[y] = false;
546            let mut new_sourced = vec![Sourced::default(); bounds.width as usize];
547
548            // This block concatenates the strings for the row into one string for bidi
549            // resolution, then maps bytes for the string to their associated cell index. It
550            // also maps the row's cell index to the font that can source all glyphs for
551            // that cell.
552            self.row.clear();
553            self.rowmap.clear();
554            let mut fontmap = Vec::with_capacity(self.rowmap.capacity());
555            for (idx, cell) in row.iter().enumerate() {
556                self.row.push_str(cell.symbol());
557                self.rowmap
558                    .resize(self.rowmap.len() + cell.symbol().len(), idx as u16);
559                fontmap.push(self.fonts.font_for_cell(cell));
560            }
561
562            let mut x = 0;
563            // rustbuzz provides a non-zero x-advance for the first character in a cluster
564            // with combining characters. The remainder of the cluster doesn't account for
565            // this advance, so if we advance prior to rendering them, we end up with all of
566            // the associated characters being offset by a cell. To combat this, we only
567            // bump the x-advance after we've finished processing all of the characters in a
568            // cell. This assumes that we 1) always get a non-zero advance at the beginning
569            // of a cluster and 2) the next cluster in the sequence starts with a non-zero
570            // advance.
571            let mut next_advance = 0;
572            let mut shape = |font: &Font,
573                             fake_bold,
574                             fake_italic,
575                             buffer: GlyphBuffer|
576             -> UnicodeBuffer {
577                let metrics = font.font();
578                let advance_scale = self.fonts.height_px() as f32 / metrics.height() as f32;
579
580                for (info, position) in buffer
581                    .glyph_infos()
582                    .iter()
583                    .zip(buffer.glyph_positions().iter())
584                {
585                    let cell_idx = self.rowmap[info.cluster as usize] as usize;
586                    let cell = &row[cell_idx];
587                    let max_width = cell.symbol().width();
588                    let sourced = &mut new_sourced[cell_idx];
589
590                    let basey = y as i32 * self.fonts.height_px() as i32
591                        + (position.y_offset as f32 * advance_scale) as i32;
592                    let mut advance = (position.x_advance as f32 * advance_scale) as i32;
593                    if advance != 0 {
594                        x += next_advance;
595                        advance =
596                            max_width as i32 * advance.signum() * self.fonts.min_width_px() as i32;
597                        next_advance = advance;
598                    }
599                    let basex = x + (position.x_offset as f32 * advance_scale) as i32;
600
601                    // This assumes that we only want to underline the first character in the
602                    // cluster, and that the remaining characters are all combining characters
603                    // which don't need an underline.
604                    let set = if advance != 0 {
605                        Modifier::BOLD | Modifier::ITALIC | Modifier::UNDERLINED
606                    } else {
607                        Modifier::BOLD | Modifier::ITALIC
608                    };
609
610                    let key = Key {
611                        style: cell.modifier.intersection(set),
612                        glyph: info.glyph_id,
613                        font: font.id(),
614                    };
615
616                    let ch = self.row[info.cluster as usize..].chars().next().unwrap();
617                    let width = (metrics
618                        .glyph_hor_advance(GlyphId(info.glyph_id as _))
619                        .unwrap_or_default() as f32
620                        * advance_scale) as u32;
621                    let chars_wide = ch.width().unwrap_or(max_width) as u32;
622                    let chars_wide = if chars_wide == 0 { 1 } else { chars_wide };
623                    let width = if width == 0 {
624                        chars_wide * self.fonts.min_width_px()
625                    } else {
626                        width
627                    };
628
629                    let cached = self.cached.get(
630                        &key,
631                        chars_wide * self.fonts.min_width_px(),
632                        self.fonts.height_px(),
633                    );
634
635                    let offset = (basey.max(0) as usize / self.fonts.height_px() as usize)
636                        .min(bounds.height as usize - 1)
637                        * bounds.width as usize
638                        + (basex.max(0) as usize / self.fonts.min_width_px() as usize)
639                            .min(bounds.width as usize - 1);
640
641                    sourced.insert((basex, basey, GlyphId(info.glyph_id as _), chars_wide));
642
643                    let mut underline_pos_min = 0;
644                    let mut underline_pos_max = 0;
645                    if key.style.contains(Modifier::UNDERLINED) {
646                        let underline_position = (metrics.ascender() as f32 * advance_scale) as u16;
647                        let underline_thickness = metrics
648                            .underline_metrics()
649                            .map(|m| (m.thickness as f32 * advance_scale) as u16)
650                            .unwrap_or(1);
651                        underline_pos_min = underline_position;
652                        underline_pos_max = underline_pos_min + underline_thickness;
653                    }
654
655                    self.rendered[offset].insert(
656                        (basex, basey, GlyphId(info.glyph_id as _)),
657                        RenderInfo {
658                            cell: y * bounds.width as usize + cell_idx,
659                            cached: *cached,
660                            underline_pos_min,
661                            underline_pos_max,
662                        },
663                    );
664                    for x_offset in 0..chars_wide as usize {
665                        self.dirty_cells.set(offset + x_offset, true);
666                    }
667
668                    if cached.cached() {
669                        continue;
670                    }
671
672                    pending_cache_updates.entry(key).or_insert_with(|| {
673                        let is_emoji = ch.is_emoji_char()
674                            && !matches!(ch.general_category_group(), GeneralCategoryGroup::Number);
675
676                        let (rect, image) = rasterize_glyph(
677                            cached,
678                            metrics,
679                            info,
680                            fake_italic & !is_emoji,
681                            fake_bold & !is_emoji,
682                            advance_scale,
683                            width,
684                        );
685                        (rect, image, is_emoji)
686                    });
687                }
688
689                buffer.clear()
690            };
691
692            let bidi = ParagraphBidiInfo::new(&self.row, None);
693            let (levels, runs) = bidi.visual_runs(0..bidi.levels.len());
694
695            let (mut current_font, mut current_fake_bold, mut current_fake_italic) = fontmap[0];
696            let mut current_level = Level::ltr();
697
698            for (level, range) in runs.into_iter().map(|run| (levels[run.start], run)) {
699                let chars = &self.row[range.clone()];
700                let cells = &self.rowmap[range.clone()];
701                for (idx, ch) in chars.char_indices() {
702                    let cell_idx = cells[idx] as usize;
703                    let (font, fake_bold, fake_italic) = fontmap[cell_idx];
704
705                    if font.id() != current_font.id()
706                        || current_fake_bold != fake_bold
707                        || current_fake_italic != fake_italic
708                        || current_level != level
709                    {
710                        let mut buffer = std::mem::take(&mut self.buffer);
711
712                        self.buffer = shape(
713                            current_font,
714                            current_fake_bold,
715                            current_fake_italic,
716                            shape_with_plan(
717                                current_font.font(),
718                                self.plan_cache.get(current_font, &mut buffer),
719                                buffer,
720                            ),
721                        );
722
723                        current_font = font;
724                        current_fake_bold = fake_bold;
725                        current_fake_italic = fake_italic;
726                        current_level = level;
727                    }
728
729                    self.buffer.add(ch, (range.start + idx) as u32);
730                }
731            }
732
733            let mut buffer = std::mem::take(&mut self.buffer);
734            self.buffer = shape(
735                current_font,
736                current_fake_bold,
737                current_fake_italic,
738                shape_with_plan(
739                    current_font.font(),
740                    self.plan_cache.get(current_font, &mut buffer),
741                    buffer,
742                ),
743            );
744
745            for (new, old) in new_sourced.into_iter().zip(sourced.iter_mut()) {
746                if new != *old {
747                    for (x, y, glyph, width) in old.difference(&new) {
748                        let cell = ((*y).max(0) as usize / self.fonts.height_px() as usize)
749                            .min(bounds.height as usize - 1)
750                            * bounds.width as usize
751                            + ((*x).max(0) as usize / self.fonts.min_width_px() as usize)
752                                .min(bounds.width as usize - 1);
753
754                        for offset_x in 0..*width as usize {
755                            if cell >= self.dirty_cells.len() {
756                                break;
757                            }
758
759                            self.dirty_cells.set(cell + offset_x, true);
760                        }
761
762                        self.rendered[cell].shift_remove(&(*x, *y, *glyph));
763                    }
764                    *old = new;
765                }
766            }
767        }
768
769        for (_, (cached, image, mask)) in pending_cache_updates {
770            self.queue.write_texture(
771                wgpu::TexelCopyTextureInfo {
772                    texture: &self.text_cache,
773                    mip_level: 0,
774                    origin: Origin3d {
775                        x: cached.x,
776                        y: cached.y,
777                        z: 0,
778                    },
779                    aspect: TextureAspect::All,
780                },
781                bytemuck::cast_slice(&image),
782                wgpu::TexelCopyBufferLayout {
783                    offset: 0,
784                    bytes_per_row: Some(cached.width * size_of::<u32>() as u32),
785                    rows_per_image: Some(cached.height),
786                },
787                Extent3d {
788                    width: cached.width,
789                    height: cached.height,
790                    depth_or_array_layers: 1,
791                },
792            );
793
794            self.queue.write_texture(
795                wgpu::TexelCopyTextureInfo {
796                    texture: &self.text_mask,
797                    mip_level: 0,
798                    origin: Origin3d {
799                        x: cached.x,
800                        y: cached.y,
801                        z: 0,
802                    },
803                    aspect: TextureAspect::All,
804                },
805                &vec![if mask { 255 } else { 0 }; (cached.width * cached.height) as usize],
806                wgpu::TexelCopyBufferLayout {
807                    offset: 0,
808                    bytes_per_row: Some(cached.width),
809                    rows_per_image: Some(cached.height),
810                },
811                Extent3d {
812                    width: cached.width,
813                    height: cached.height,
814                    depth_or_array_layers: 1,
815                },
816            )
817        }
818
819        if self.post_process.needs_update() || self.dirty_cells.any() {
820            self.bg_vertices.clear();
821            self.text_vertices.clear();
822            self.text_indices.clear();
823
824            let mut index_offset = 0;
825            for index in self.dirty_cells.iter_ones() {
826                let cell = &self.cells[index];
827                let to_render = &self.rendered[index];
828
829                let reverse = cell.modifier.contains(Modifier::REVERSED);
830                let bg_color = if reverse {
831                    c2c(cell.fg, self.reset_fg)
832                } else {
833                    c2c(cell.bg, self.reset_bg)
834                };
835
836                let [r, g, b] = bg_color;
837                let bg_color_u32: u32 = u32::from_be_bytes([r, g, b, 255]);
838
839                let y = (index as u32 / bounds.width as u32 * self.fonts.height_px()) as f32;
840                let x = (index as u32 % bounds.width as u32 * self.fonts.min_width_px()) as f32;
841                for offset_x in 0..cell.symbol().width() {
842                    let x = x + (offset_x as u32 * self.fonts.min_width_px()) as f32;
843                    self.bg_vertices.push(TextBgVertexMember {
844                        vertex: [x, y],
845                        bg_color: bg_color_u32,
846                    });
847                    self.bg_vertices.push(TextBgVertexMember {
848                        vertex: [x + self.fonts.min_width_px() as f32, y],
849                        bg_color: bg_color_u32,
850                    });
851                    self.bg_vertices.push(TextBgVertexMember {
852                        vertex: [x, y + self.fonts.height_px() as f32],
853                        bg_color: bg_color_u32,
854                    });
855                    self.bg_vertices.push(TextBgVertexMember {
856                        vertex: [
857                            x + self.fonts.min_width_px() as f32,
858                            y + self.fonts.height_px() as f32,
859                        ],
860                        bg_color: bg_color_u32,
861                    });
862                }
863
864                for (
865                    (x, y, _),
866                    RenderInfo {
867                        cell,
868                        cached,
869                        underline_pos_min,
870                        underline_pos_max,
871                    },
872                ) in to_render.iter()
873                {
874                    let cell = &self.cells[*cell];
875                    let reverse = cell.modifier.contains(Modifier::REVERSED);
876                    let fg_color = if reverse {
877                        c2c(cell.bg, self.reset_bg)
878                    } else {
879                        c2c(cell.fg, self.reset_fg)
880                    };
881
882                    let alpha = if cell.modifier.contains(Modifier::HIDDEN)
883                        | (cell.modifier.contains(Modifier::RAPID_BLINK) & !self.show_fast)
884                        | (cell.modifier.contains(Modifier::SLOW_BLINK) & !self.show_slow)
885                    {
886                        0
887                    } else if cell.modifier.contains(Modifier::DIM) {
888                        127
889                    } else {
890                        255
891                    };
892
893                    let underline_color = fg_color;
894                    let [r, g, b] = fg_color;
895                    let fg_color: u32 = u32::from_be_bytes([r, g, b, alpha]);
896
897                    let [r, g, b] = underline_color;
898                    let underline_color = u32::from_be_bytes([r, g, b, alpha]);
899
900                    for offset_x in (0..cached.width).step_by(self.fonts.min_width_px() as usize) {
901                        self.text_indices.push([
902                            index_offset,     // x, y
903                            index_offset + 1, // x + w, y
904                            index_offset + 2, // x, y + h
905                            index_offset + 2, // x, y + h
906                            index_offset + 3, // x + w, y + h
907                            index_offset + 1, // x + w y
908                        ]);
909                        index_offset += 4;
910
911                        let x = *x as f32 + offset_x as f32;
912                        let y = *y as f32;
913                        let uvx = cached.x + offset_x;
914                        let uvy = cached.y;
915
916                        let underline_pos = ((*underline_pos_min as u32 + uvy) << 16)
917                            | (*underline_pos_max as u32 + uvy);
918
919                        // 0
920                        self.text_vertices.push(TextVertexMember {
921                            vertex: [x, y],
922                            uv: [uvx as f32, uvy as f32],
923                            fg_color,
924                            underline_pos,
925                            underline_color,
926                        });
927                        // 1
928                        self.text_vertices.push(TextVertexMember {
929                            vertex: [x + self.fonts.min_width_px() as f32, y],
930                            uv: [uvx as f32 + self.fonts.min_width_px() as f32, uvy as f32],
931                            fg_color,
932                            underline_pos,
933                            underline_color,
934                        });
935                        // 2
936                        self.text_vertices.push(TextVertexMember {
937                            vertex: [x, y + self.fonts.height_px() as f32],
938                            uv: [uvx as f32, uvy as f32 + self.fonts.height_px() as f32],
939                            fg_color,
940                            underline_pos,
941                            underline_color,
942                        });
943                        // 3
944                        self.text_vertices.push(TextVertexMember {
945                            vertex: [
946                                x + self.fonts.min_width_px() as f32,
947                                y + self.fonts.height_px() as f32,
948                            ],
949                            uv: [
950                                uvx as f32 + self.fonts.min_width_px() as f32,
951                                uvy as f32 + self.fonts.height_px() as f32,
952                            ],
953                            fg_color,
954                            underline_pos,
955                            underline_color,
956                        });
957                    }
958                }
959            }
960
961            self.render();
962        }
963
964        Ok(())
965    }
966
967    fn clear_region(&mut self, clear_type: ClearType) -> std::io::Result<()> {
968        let bounds = self.size()?;
969        let line_start = self.cursor.1 as usize * bounds.width as usize;
970        let idx = line_start + self.cursor.0 as usize;
971
972        match clear_type {
973            ClearType::All => self.clear(),
974            ClearType::AfterCursor => {
975                self.cells.truncate(idx + 1);
976                Ok(())
977            }
978            ClearType::BeforeCursor => {
979                self.cells[..idx].fill(Cell::EMPTY);
980                Ok(())
981            }
982            ClearType::CurrentLine => {
983                self.cells[line_start..line_start + bounds.width as usize].fill(Cell::EMPTY);
984                Ok(())
985            }
986            ClearType::UntilNewLine => {
987                let remain = (bounds.width - self.cursor.0) as usize;
988                self.cells[idx..idx + remain].fill(Cell::EMPTY);
989                Ok(())
990            }
991        }
992    }
993}
994
995fn rasterize_glyph(
996    cached: Entry,
997    metrics: &rustybuzz::Face,
998    info: &rustybuzz::GlyphInfo,
999    fake_italic: bool,
1000    fake_bold: bool,
1001    advance_scale: f32,
1002    actual_width: u32,
1003) -> (CacheRect, Vec<u32>) {
1004    let scale = cached.width as f32 / actual_width as f32;
1005    let computed_offset_x = -(cached.width as f32 * (1.0 - scale));
1006    let computed_offset_y = cached.height as f32 * (1.0 - scale);
1007    let scale = scale * advance_scale * 2.0;
1008
1009    let skew = if fake_italic {
1010        Transform::new(
1011            /* scale x */ 1.0,
1012            /* skew x */ 0.0,
1013            /* skew y */ -0.25,
1014            /* scale y */ 1.0,
1015            /* translate x */ -0.25 * cached.width as f32,
1016            /* translate y */ 0.0,
1017        )
1018    } else {
1019        Transform::default()
1020    };
1021
1022    let mut image = vec![0u32; cached.width as usize * 2 * cached.height as usize * 2];
1023    let mut target = DrawTarget::from_backing(
1024        cached.width as i32 * 2,
1025        cached.height as i32 * 2,
1026        &mut image[..],
1027    );
1028
1029    let mut painter = Painter::new(
1030        metrics,
1031        &mut target,
1032        skew,
1033        scale,
1034        metrics.ascender() as f32 * scale + computed_offset_y,
1035        computed_offset_x,
1036    );
1037    if metrics
1038        .paint_color_glyph(
1039            GlyphId(info.glyph_id as _),
1040            0,
1041            RgbaColor::new(255, 255, 255, 255),
1042            &mut painter,
1043        )
1044        .is_some()
1045    {
1046        let mut final_image = DrawTarget::new(cached.width as i32, cached.height as i32);
1047        final_image.draw_image_with_size_at(
1048            cached.width as f32,
1049            cached.height as f32,
1050            0.,
1051            0.,
1052            &raqote::Image {
1053                width: cached.width as i32 * 2,
1054                height: cached.height as i32 * 2,
1055                data: &image,
1056            },
1057            &DrawOptions {
1058                blend_mode: raqote::BlendMode::Src,
1059                antialias: raqote::AntialiasMode::None,
1060                ..Default::default()
1061            },
1062        );
1063
1064        let mut final_image = final_image.into_vec();
1065        for argb in final_image.iter_mut() {
1066            let [a, r, g, b] = argb.to_be_bytes();
1067            *argb = u32::from_le_bytes([r, g, b, a]);
1068        }
1069
1070        return (*cached, final_image);
1071    }
1072
1073    if let Some(raster) = metrics.glyph_raster_image(GlyphId(info.glyph_id as _), u16::MAX) {
1074        if let Some(value) = extract_color_image(&mut image, raster, cached, advance_scale) {
1075            return value;
1076        }
1077    }
1078
1079    let mut render = Outline::default();
1080    if let Some(bounds) = metrics.outline_glyph(GlyphId(info.glyph_id as _), &mut render) {
1081        let path = render.finish();
1082
1083        // Some fonts return bounds that are entirely negative. I'm not sure why this
1084        // is, but it means the glyph won't render at all. We check for this here and
1085        // offset it if so. This seems to let those fonts render correctly.
1086        let x_off = if bounds.x_max < 0 {
1087            -bounds.x_min as f32
1088        } else {
1089            0.
1090        };
1091        let x_off = x_off * scale + computed_offset_x;
1092        let y_off = metrics.ascender() as f32 * scale + computed_offset_y;
1093
1094        let mut target = DrawTarget::from_backing(
1095            cached.width as i32 * 2,
1096            cached.height as i32 * 2,
1097            &mut image[..],
1098        );
1099        target.set_transform(
1100            &Transform::scale(scale, -scale)
1101                .then(&skew)
1102                .then_translate((x_off, y_off).into()),
1103        );
1104
1105        target.fill(
1106            &path,
1107            &raqote::Source::Solid(SolidSource::from_unpremultiplied_argb(255, 255, 255, 255)),
1108            &DrawOptions::default(),
1109        );
1110
1111        if fake_bold {
1112            target.stroke(
1113                &path,
1114                &raqote::Source::Solid(SolidSource::from_unpremultiplied_argb(255, 255, 255, 255)),
1115                &StrokeStyle {
1116                    width: 1.5,
1117                    ..Default::default()
1118                },
1119                &DrawOptions::new(),
1120            );
1121        }
1122
1123        let mut final_image = DrawTarget::new(cached.width as i32, cached.height as i32);
1124        final_image.draw_image_with_size_at(
1125            cached.width as f32,
1126            cached.height as f32,
1127            0.,
1128            0.,
1129            &raqote::Image {
1130                width: cached.width as i32 * 2,
1131                height: cached.height as i32 * 2,
1132                data: &image,
1133            },
1134            &DrawOptions {
1135                blend_mode: raqote::BlendMode::Src,
1136                antialias: raqote::AntialiasMode::None,
1137                ..Default::default()
1138            },
1139        );
1140
1141        return (*cached, final_image.into_vec());
1142    }
1143
1144    if let Some(raster) = metrics.glyph_raster_image(GlyphId(info.glyph_id as _), u16::MAX) {
1145        if let Some(value) = extract_bw_image(&mut image, raster, cached, advance_scale) {
1146            return value;
1147        }
1148    }
1149
1150    (
1151        *cached,
1152        vec![0u32; cached.width as usize * cached.height as usize],
1153    )
1154}
1155
1156fn extract_color_image(
1157    image: &mut Vec<u32>,
1158    raster: RasterGlyphImage,
1159    cached: Entry,
1160    scale: f32,
1161) -> Option<(CacheRect, Vec<u32>)> {
1162    match raster.format {
1163        RasterImageFormat::PNG => {
1164            #[cfg(feature = "png")]
1165            {
1166                let decoder = png::Decoder::new(std::io::Cursor::new(raster.data));
1167                if let Ok(mut info) = decoder.read_info() {
1168                    image.resize(info.output_buffer_size() / size_of::<u32>(), 0);
1169                    if info.next_frame(bytemuck::cast_slice_mut(image)).is_err() {
1170                        return None;
1171                    }
1172
1173                    for rgba in image.iter_mut() {
1174                        let [r, g, b, a] = rgba.to_be_bytes();
1175                        *rgba = u32::from_be_bytes([a, r, g, b]);
1176                    }
1177                } else {
1178                    return None;
1179                }
1180            }
1181            #[cfg(not(feature = "png"))]
1182            return None;
1183        }
1184        RasterImageFormat::BitmapPremulBgra32 => {
1185            image.resize(raster.width as usize * raster.height as usize, 0);
1186            for (y, row) in raster.data.chunks(raster.width as usize * 4).enumerate() {
1187                for (x, pixel) in row.chunks(4).enumerate() {
1188                    let pixel: &[u8; 4] = pixel.try_into().expect("Invalid chunk size");
1189                    let [b, g, r, a] = *pixel;
1190                    let pixel = u32::from_be_bytes([
1191                        a,
1192                        r.saturating_mul(255 / a),
1193                        g.saturating_mul(255 / a),
1194                        b.saturating_mul(255 / a),
1195                    ]);
1196                    image[y * raster.width as usize + x] = pixel;
1197                }
1198            }
1199        }
1200        _ => return None,
1201    }
1202
1203    let mut final_image = DrawTarget::new(cached.width as i32, cached.height as i32);
1204    final_image.draw_image_with_size_at(
1205        cached.width as f32,
1206        cached.height as f32,
1207        raster.x as f32 * scale,
1208        raster.y as f32 * scale,
1209        &raqote::Image {
1210            width: raster.width as i32,
1211            height: raster.height as i32,
1212            data: &*image,
1213        },
1214        &DrawOptions {
1215            blend_mode: raqote::BlendMode::Src,
1216            antialias: raqote::AntialiasMode::None,
1217            ..Default::default()
1218        },
1219    );
1220
1221    let mut final_image = final_image.into_vec();
1222    for argb in final_image.iter_mut() {
1223        let [a, r, g, b] = argb.to_be_bytes();
1224        *argb = u32::from_le_bytes([r, g, b, a]);
1225    }
1226
1227    Some((*cached, final_image))
1228}
1229
1230fn extract_bw_image(
1231    image: &mut Vec<u32>,
1232    raster: RasterGlyphImage,
1233    cached: Entry,
1234    scale: f32,
1235) -> Option<(CacheRect, Vec<u32>)> {
1236    image.resize(raster.width as usize * raster.height as usize, 0);
1237
1238    match raster.format {
1239        RasterImageFormat::BitmapMono => {
1240            from_gray_unpacked::<1, 2>(image, raster, LUT_1);
1241        }
1242        RasterImageFormat::BitmapMonoPacked => {
1243            from_gray_packed::<1, 2>(image, raster, LUT_1);
1244        }
1245        RasterImageFormat::BitmapGray2 => {
1246            from_gray_unpacked::<2, 4>(image, raster, LUT_2);
1247        }
1248        RasterImageFormat::BitmapGray2Packed => {
1249            from_gray_packed::<2, 4>(image, raster, LUT_2);
1250        }
1251        RasterImageFormat::BitmapGray4 => {
1252            from_gray_unpacked::<4, 16>(image, raster, LUT_4);
1253        }
1254        RasterImageFormat::BitmapGray4Packed => {
1255            from_gray_packed::<4, 16>(image, raster, LUT_4);
1256        }
1257        RasterImageFormat::BitmapGray8 => {
1258            for (byte, dst) in raster.data.iter().zip(image.iter_mut()) {
1259                *dst = u32::from_be_bytes([*byte, 255, 255, 255]);
1260            }
1261        }
1262        _ => return None,
1263    }
1264
1265    let mut final_image = DrawTarget::new(cached.width as i32, cached.height as i32);
1266    final_image.draw_image_with_size_at(
1267        cached.width as f32,
1268        cached.height as f32,
1269        raster.x as f32 * scale,
1270        raster.y as f32 * scale,
1271        &raqote::Image {
1272            width: raster.width as i32,
1273            height: raster.height as i32,
1274            data: &*image,
1275        },
1276        &DrawOptions {
1277            blend_mode: raqote::BlendMode::Src,
1278            antialias: raqote::AntialiasMode::None,
1279            ..Default::default()
1280        },
1281    );
1282
1283    let mut final_image = final_image.into_vec();
1284    for argb in final_image.iter_mut() {
1285        let [a, r, g, b] = argb.to_be_bytes();
1286        *argb = u32::from_le_bytes([r, g, b, a]);
1287    }
1288
1289    Some((*cached, final_image))
1290}
1291
1292fn from_gray_unpacked<const BITS: usize, const ENTRIES: usize>(
1293    image: &mut [u32],
1294    raster: RasterGlyphImage,
1295    steps: [u8; ENTRIES],
1296) {
1297    for (bits, dst) in raster
1298        .data
1299        .chunks((raster.width as usize / (8 / BITS)) + 1)
1300        .zip(image.chunks_mut(raster.width as usize))
1301    {
1302        let bits = BitSlice::<_, Lsb0>::from_slice(bits);
1303        for (bits, dst) in bits.chunks(BITS).zip(dst.iter_mut()) {
1304            let mut index = 0;
1305            for idx in bits.iter_ones() {
1306                index |= 1 << (BITS - idx - 1);
1307            }
1308            let value = steps[index as usize];
1309            *dst = u32::from_be_bytes([value, 255, 255, 255]);
1310        }
1311    }
1312}
1313
1314fn from_gray_packed<const BITS: usize, const ENTRIES: usize>(
1315    image: &mut [u32],
1316    raster: RasterGlyphImage,
1317    steps: [u8; ENTRIES],
1318) {
1319    let bits = BitSlice::<_, Lsb0>::from_slice(raster.data);
1320    for (bits, dst) in bits.chunks(BITS).zip(image.iter_mut()) {
1321        let mut index = 0;
1322        for idx in bits.iter_ones() {
1323            index |= 1 << (BITS - idx - 1);
1324        }
1325        let value = steps[index as usize];
1326        *dst = u32::from_be_bytes([value, 255, 255, 255]);
1327    }
1328}
1329
1330const LUT_1: [u8; 2] = [0, 255];
1331const LUT_2: [u8; 4] = [0, 255 / 3, 2 * (255 / 3), 255];
1332const LUT_4: [u8; 16] = [
1333    0,
1334    (255 / 15),
1335    2 * (255 / 15),
1336    3 * (255 / 15),
1337    4 * (255 / 15),
1338    5 * (255 / 15),
1339    6 * (255 / 15),
1340    7 * (255 / 15),
1341    8 * (255 / 15),
1342    9 * (255 / 15),
1343    10 * (255 / 15),
1344    11 * (255 / 15),
1345    12 * (255 / 15),
1346    13 * (255 / 15),
1347    14 * (255 / 15),
1348    255,
1349];
1350
1351#[cfg(test)]
1352mod tests {
1353    use std::num::NonZeroU32;
1354
1355    use image::{
1356        load_from_memory,
1357        GenericImageView,
1358        ImageBuffer,
1359        Rgba,
1360    };
1361    use ratatui::{
1362        style::{
1363            Color,
1364            Stylize,
1365        },
1366        text::Line,
1367        widgets::{
1368            Block,
1369            Paragraph,
1370        },
1371        Terminal,
1372    };
1373    use rustybuzz::ttf_parser::{
1374        RasterGlyphImage,
1375        RasterImageFormat,
1376    };
1377    use serial_test::serial;
1378    use wgpu::{
1379        CommandEncoderDescriptor,
1380        Device,
1381        Extent3d,
1382        Queue,
1383        TextureFormat,
1384    };
1385
1386    use crate::{
1387        backend::{
1388            wgpu_backend::{
1389                extract_bw_image,
1390                LUT_2,
1391                LUT_4,
1392            },
1393            HeadlessSurface,
1394        },
1395        shaders::DefaultPostProcessor,
1396        utils::text_atlas::{
1397            CacheRect,
1398            Entry,
1399        },
1400        Builder,
1401        Dimensions,
1402        Font,
1403    };
1404
1405    fn tex2buffer(device: &Device, queue: &Queue, surface: &HeadlessSurface) {
1406        let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor::default());
1407        encoder.copy_texture_to_buffer(
1408            surface.texture.as_ref().unwrap().as_image_copy(),
1409            wgpu::TexelCopyBufferInfo {
1410                buffer: surface.buffer.as_ref().unwrap(),
1411                layout: wgpu::TexelCopyBufferLayout {
1412                    offset: 0,
1413                    bytes_per_row: Some(surface.buffer_width),
1414                    rows_per_image: Some(surface.height),
1415                },
1416            },
1417            Extent3d {
1418                width: surface.width,
1419                height: surface.height,
1420                depth_or_array_layers: 1,
1421            },
1422        );
1423        queue.submit(Some(encoder.finish()));
1424    }
1425
1426    #[test]
1427    #[serial]
1428    fn a_z() {
1429        let mut terminal = Terminal::new(
1430            futures_lite::future::block_on(
1431                Builder::<DefaultPostProcessor>::from_font(
1432                    Font::new(include_bytes!("fonts/CascadiaMono-Regular.ttf"))
1433                        .expect("Invalid font file"),
1434                )
1435                .with_width_and_height(Dimensions {
1436                    width: NonZeroU32::new(512).unwrap(),
1437                    height: NonZeroU32::new(72).unwrap(),
1438                })
1439                .build_headless(),
1440            )
1441            .unwrap(),
1442        )
1443        .unwrap();
1444
1445        terminal
1446            .draw(|f| {
1447                let block = Block::bordered();
1448                let area = block.inner(f.area());
1449                f.render_widget(block, f.area());
1450                f.render_widget(Paragraph::new("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), area);
1451            })
1452            .unwrap();
1453
1454        let surface = &terminal.backend().surface;
1455        tex2buffer(
1456            &terminal.backend().device,
1457            &terminal.backend().queue,
1458            surface,
1459        );
1460        {
1461            let buffer = surface.buffer.as_ref().unwrap().slice(..);
1462
1463            let (send, recv) = oneshot::channel();
1464            buffer.map_async(wgpu::MapMode::Read, move |data| {
1465                send.send(data).unwrap();
1466            });
1467            terminal.backend().device.poll(wgpu::MaintainBase::Wait);
1468            recv.recv().unwrap().unwrap();
1469
1470            let data = buffer.get_mapped_range();
1471            let image =
1472                ImageBuffer::<Rgba<u8>, _>::from_raw(surface.width, surface.height, data).unwrap();
1473
1474            let pixels = image.pixels().copied().collect::<Vec<_>>();
1475            let golden = load_from_memory(include_bytes!("goldens/a_z.png")).unwrap();
1476            let golden_pixels = golden.pixels().map(|(_, _, px)| px).collect::<Vec<_>>();
1477
1478            assert!(
1479                pixels == golden_pixels,
1480                "Rendered image differs from golden"
1481            );
1482        }
1483
1484        surface.buffer.as_ref().unwrap().unmap();
1485    }
1486
1487    #[test]
1488    #[serial]
1489    fn arabic() {
1490        let mut terminal = Terminal::new(
1491            futures_lite::future::block_on(
1492                Builder::<DefaultPostProcessor>::from_font(
1493                    Font::new(include_bytes!("fonts/CascadiaMono-Regular.ttf"))
1494                        .expect("Invalid font file"),
1495                )
1496                .with_width_and_height(Dimensions {
1497                    width: NonZeroU32::new(256).unwrap(),
1498                    height: NonZeroU32::new(72).unwrap(),
1499                })
1500                .build_headless(),
1501            )
1502            .unwrap(),
1503        )
1504        .unwrap();
1505
1506        terminal
1507            .draw(|f| {
1508                let block = Block::bordered();
1509                let area = block.inner(f.area());
1510                f.render_widget(block, f.area());
1511                f.render_widget(Paragraph::new("مرحبا بالعالم"), area);
1512            })
1513            .unwrap();
1514
1515        let surface = &terminal.backend().surface;
1516        tex2buffer(
1517            &terminal.backend().device,
1518            &terminal.backend().queue,
1519            surface,
1520        );
1521        {
1522            let buffer = surface.buffer.as_ref().unwrap().slice(..);
1523
1524            let (send, recv) = oneshot::channel();
1525            buffer.map_async(wgpu::MapMode::Read, move |data| {
1526                send.send(data).unwrap();
1527            });
1528            terminal.backend().device.poll(wgpu::MaintainBase::Wait);
1529            recv.recv().unwrap().unwrap();
1530
1531            let data = buffer.get_mapped_range();
1532            let image =
1533                ImageBuffer::<Rgba<u8>, _>::from_raw(surface.width, surface.height, data).unwrap();
1534
1535            let pixels = image.pixels().copied().collect::<Vec<_>>();
1536            let golden = load_from_memory(include_bytes!("goldens/arabic.png")).unwrap();
1537            let golden_pixels = golden.pixels().map(|(_, _, px)| px).collect::<Vec<_>>();
1538
1539            assert!(
1540                pixels == golden_pixels,
1541                "Rendered image differs from golden"
1542            );
1543        }
1544
1545        surface.buffer.as_ref().unwrap().unmap();
1546    }
1547
1548    #[test]
1549    #[serial]
1550    fn really_wide() {
1551        let mut terminal = Terminal::new(
1552            futures_lite::future::block_on(
1553                Builder::<DefaultPostProcessor>::from_font(
1554                    Font::new(include_bytes!("fonts/Fairfax.ttf")).expect("Invalid font file"),
1555                )
1556                .with_width_and_height(Dimensions {
1557                    width: NonZeroU32::new(512).unwrap(),
1558                    height: NonZeroU32::new(72).unwrap(),
1559                })
1560                .build_headless(),
1561            )
1562            .unwrap(),
1563        )
1564        .unwrap();
1565
1566        terminal
1567            .draw(|f| {
1568                let block = Block::bordered();
1569                let area = block.inner(f.area());
1570                f.render_widget(block, f.area());
1571                f.render_widget(Paragraph::new("Hello, world!"), area);
1572            })
1573            .unwrap();
1574
1575        let surface = &terminal.backend().surface;
1576        tex2buffer(
1577            &terminal.backend().device,
1578            &terminal.backend().queue,
1579            surface,
1580        );
1581        {
1582            let buffer = surface.buffer.as_ref().unwrap().slice(..);
1583
1584            let (send, recv) = oneshot::channel();
1585            buffer.map_async(wgpu::MapMode::Read, move |data| {
1586                send.send(data).unwrap();
1587            });
1588            terminal.backend().device.poll(wgpu::MaintainBase::Wait);
1589            recv.recv().unwrap().unwrap();
1590
1591            let data = buffer.get_mapped_range();
1592            let image =
1593                ImageBuffer::<Rgba<u8>, _>::from_raw(surface.width, surface.height, data).unwrap();
1594
1595            let pixels = image.pixels().copied().collect::<Vec<_>>();
1596            let golden = load_from_memory(include_bytes!("goldens/really_wide.png")).unwrap();
1597            let golden_pixels = golden.pixels().map(|(_, _, px)| px).collect::<Vec<_>>();
1598
1599            assert!(
1600                pixels == golden_pixels,
1601                "Rendered image differs from golden"
1602            );
1603        }
1604
1605        surface.buffer.as_ref().unwrap().unmap();
1606    }
1607
1608    #[test]
1609    #[serial]
1610    fn mixed() {
1611        let mut terminal = Terminal::new(
1612            futures_lite::future::block_on(
1613                Builder::<DefaultPostProcessor>::from_font(
1614                    Font::new(include_bytes!("fonts/CascadiaMono-Regular.ttf"))
1615                        .expect("Invalid font file"),
1616                )
1617                .with_width_and_height(Dimensions {
1618                    width: NonZeroU32::new(512).unwrap(),
1619                    height: NonZeroU32::new(72).unwrap(),
1620                })
1621                .build_headless(),
1622            )
1623            .unwrap(),
1624        )
1625        .unwrap();
1626
1627        terminal
1628            .draw(|f| {
1629                let block = Block::bordered();
1630                let area = block.inner(f.area());
1631                f.render_widget(block, f.area());
1632                f.render_widget(
1633                    Paragraph::new("Hello World! مرحبا بالعالم 0123456789000000000"),
1634                    area,
1635                );
1636            })
1637            .unwrap();
1638
1639        let surface = &terminal.backend().surface;
1640        tex2buffer(
1641            &terminal.backend().device,
1642            &terminal.backend().queue,
1643            surface,
1644        );
1645        {
1646            let buffer = surface.buffer.as_ref().unwrap().slice(..);
1647
1648            let (send, recv) = oneshot::channel();
1649            buffer.map_async(wgpu::MapMode::Read, move |data| {
1650                send.send(data).unwrap();
1651            });
1652            terminal.backend().device.poll(wgpu::MaintainBase::Wait);
1653            recv.recv().unwrap().unwrap();
1654
1655            let data = buffer.get_mapped_range();
1656            let image =
1657                ImageBuffer::<Rgba<u8>, _>::from_raw(surface.width, surface.height, data).unwrap();
1658
1659            let pixels = image.pixels().copied().collect::<Vec<_>>();
1660            let golden = load_from_memory(include_bytes!("goldens/mixed.png")).unwrap();
1661            let golden_pixels = golden.pixels().map(|(_, _, px)| px).collect::<Vec<_>>();
1662
1663            assert!(
1664                pixels == golden_pixels,
1665                "Rendered image differs from golden"
1666            );
1667        }
1668
1669        surface.buffer.as_ref().unwrap().unmap();
1670    }
1671
1672    #[test]
1673    #[serial]
1674    fn mixed_colors() {
1675        let mut terminal = Terminal::new(
1676            futures_lite::future::block_on(
1677                Builder::<DefaultPostProcessor>::from_font(
1678                    Font::new(include_bytes!("fonts/CascadiaMono-Regular.ttf"))
1679                        .expect("Invalid font file"),
1680                )
1681                .with_width_and_height(Dimensions {
1682                    width: NonZeroU32::new(512).unwrap(),
1683                    height: NonZeroU32::new(72).unwrap(),
1684                })
1685                .build_headless(),
1686            )
1687            .unwrap(),
1688        )
1689        .unwrap();
1690
1691        terminal
1692            .draw(|f| {
1693                let block = Block::bordered();
1694                let area = block.inner(f.area());
1695                f.render_widget(block, f.area());
1696                f.render_widget(
1697                    Paragraph::new(Line::from(vec![
1698                        "Hello World!".green(),
1699                        "مرحبا بالعالم".blue(),
1700                        "0123456789".dim(),
1701                    ])),
1702                    area,
1703                );
1704            })
1705            .unwrap();
1706
1707        let surface = &terminal.backend().surface;
1708        tex2buffer(
1709            &terminal.backend().device,
1710            &terminal.backend().queue,
1711            surface,
1712        );
1713        {
1714            let buffer = surface.buffer.as_ref().unwrap().slice(..);
1715
1716            let (send, recv) = oneshot::channel();
1717            buffer.map_async(wgpu::MapMode::Read, move |data| {
1718                send.send(data).unwrap();
1719            });
1720            terminal.backend().device.poll(wgpu::MaintainBase::Wait);
1721            recv.recv().unwrap().unwrap();
1722
1723            let data = buffer.get_mapped_range();
1724            let image =
1725                ImageBuffer::<Rgba<u8>, _>::from_raw(surface.width, surface.height, data).unwrap();
1726
1727            let pixels = image.pixels().copied().collect::<Vec<_>>();
1728            let golden = load_from_memory(include_bytes!("goldens/mixed_colors.png")).unwrap();
1729            let golden_pixels = golden.pixels().map(|(_, _, px)| px).collect::<Vec<_>>();
1730
1731            assert!(
1732                pixels == golden_pixels,
1733                "Rendered image differs from golden"
1734            );
1735        }
1736
1737        surface.buffer.as_ref().unwrap().unmap();
1738    }
1739
1740    #[test]
1741    #[serial]
1742    fn overlap() {
1743        let mut terminal = Terminal::new(
1744            futures_lite::future::block_on(
1745                Builder::<DefaultPostProcessor>::from_font(
1746                    Font::new(include_bytes!("fonts/Fairfax.ttf")).expect("Invalid font file"),
1747                )
1748                .with_width_and_height(Dimensions {
1749                    width: NonZeroU32::new(256).unwrap(),
1750                    height: NonZeroU32::new(72).unwrap(),
1751                })
1752                .build_headless(),
1753            )
1754            .unwrap(),
1755        )
1756        .unwrap();
1757
1758        terminal
1759            .draw(|f| {
1760                let block = Block::bordered();
1761                let area = block.inner(f.area());
1762                f.render_widget(block, f.area());
1763                f.render_widget(Paragraph::new("H̴̢͕̠͖͇̻͓̙̞͔͕͓̰͋͛͂̃̌͂͆͜͠".underlined()), area);
1764            })
1765            .unwrap();
1766
1767        let surface = &terminal.backend().surface;
1768        tex2buffer(
1769            &terminal.backend().device,
1770            &terminal.backend().queue,
1771            surface,
1772        );
1773        {
1774            let buffer = surface.buffer.as_ref().unwrap().slice(..);
1775
1776            let (send, recv) = oneshot::channel();
1777            buffer.map_async(wgpu::MapMode::Read, move |data| {
1778                send.send(data).unwrap();
1779            });
1780            terminal.backend().device.poll(wgpu::MaintainBase::Wait);
1781            recv.recv().unwrap().unwrap();
1782
1783            let data = buffer.get_mapped_range();
1784            let image =
1785                ImageBuffer::<Rgba<u8>, _>::from_raw(surface.width, surface.height, data).unwrap();
1786
1787            let pixels = image.pixels().copied().collect::<Vec<_>>();
1788            let golden = load_from_memory(include_bytes!("goldens/overlap_initial.png")).unwrap();
1789            let golden_pixels = golden.pixels().map(|(_, _, px)| px).collect::<Vec<_>>();
1790
1791            assert!(
1792                pixels == golden_pixels,
1793                "Rendered image differs from golden"
1794            );
1795        }
1796        surface.buffer.as_ref().unwrap().unmap();
1797
1798        terminal
1799            .draw(|f| {
1800                let block = Block::bordered();
1801                let area = block.inner(f.area());
1802                f.render_widget(block, f.area());
1803                f.render_widget(Paragraph::new("H".underlined()), area);
1804            })
1805            .unwrap();
1806
1807        let surface = &terminal.backend().surface;
1808        tex2buffer(
1809            &terminal.backend().device,
1810            &terminal.backend().queue,
1811            surface,
1812        );
1813        {
1814            let buffer = surface.buffer.as_ref().unwrap().slice(..);
1815
1816            let (send, recv) = oneshot::channel();
1817            buffer.map_async(wgpu::MapMode::Read, move |data| {
1818                send.send(data).unwrap();
1819            });
1820            terminal.backend().device.poll(wgpu::MaintainBase::Wait);
1821            recv.recv().unwrap().unwrap();
1822
1823            let data = buffer.get_mapped_range();
1824            let image =
1825                ImageBuffer::<Rgba<u8>, _>::from_raw(surface.width, surface.height, data).unwrap();
1826
1827            let pixels = image.pixels().copied().collect::<Vec<_>>();
1828            let golden = load_from_memory(include_bytes!("goldens/overlap_post.png")).unwrap();
1829            let golden_pixels = golden.pixels().map(|(_, _, px)| px).collect::<Vec<_>>();
1830
1831            assert!(
1832                pixels == golden_pixels,
1833                "Rendered image differs from golden"
1834            );
1835        }
1836
1837        surface.buffer.as_ref().unwrap().unmap();
1838    }
1839
1840    #[test]
1841    #[serial]
1842    fn overlap_colors() {
1843        let mut terminal = Terminal::new(
1844            futures_lite::future::block_on(
1845                Builder::<DefaultPostProcessor>::from_font(
1846                    Font::new(include_bytes!("fonts/Fairfax.ttf")).expect("Invalid font file"),
1847                )
1848                .with_width_and_height(Dimensions {
1849                    width: NonZeroU32::new(256).unwrap(),
1850                    height: NonZeroU32::new(72).unwrap(),
1851                })
1852                .build_headless(),
1853            )
1854            .unwrap(),
1855        )
1856        .unwrap();
1857
1858        terminal
1859            .draw(|f| {
1860                let block = Block::bordered();
1861                let area = block.inner(f.area());
1862                f.render_widget(block, f.area());
1863                f.render_widget(Paragraph::new("H̴̢͕̠͖͇̻͓̙̞͔͕͓̰͋͛͂̃̌͂͆͜͠".blue().on_red().underlined()), area);
1864            })
1865            .unwrap();
1866
1867        let surface = &terminal.backend().surface;
1868        tex2buffer(
1869            &terminal.backend().device,
1870            &terminal.backend().queue,
1871            surface,
1872        );
1873        {
1874            let buffer = surface.buffer.as_ref().unwrap().slice(..);
1875
1876            let (send, recv) = oneshot::channel();
1877            buffer.map_async(wgpu::MapMode::Read, move |data| {
1878                send.send(data).unwrap();
1879            });
1880            terminal.backend().device.poll(wgpu::MaintainBase::Wait);
1881            recv.recv().unwrap().unwrap();
1882
1883            let data = buffer.get_mapped_range();
1884            let image =
1885                ImageBuffer::<Rgba<u8>, _>::from_raw(surface.width, surface.height, data).unwrap();
1886
1887            let pixels = image.pixels().copied().collect::<Vec<_>>();
1888            let golden = load_from_memory(include_bytes!("goldens/overlap_colors.png")).unwrap();
1889            let golden_pixels = golden.pixels().map(|(_, _, px)| px).collect::<Vec<_>>();
1890
1891            assert!(
1892                pixels == golden_pixels,
1893                "Rendered image differs from golden"
1894            );
1895        }
1896        surface.buffer.as_ref().unwrap().unmap();
1897    }
1898
1899    #[test]
1900    #[serial]
1901    fn rgb_conversion() {
1902        let mut terminal = Terminal::new(
1903            futures_lite::future::block_on(
1904                Builder::<DefaultPostProcessor>::from_font(
1905                    Font::new(include_bytes!("fonts/Fairfax.ttf")).expect("Invalid font file"),
1906                )
1907                .with_width_and_height(Dimensions {
1908                    width: NonZeroU32::new(256).unwrap(),
1909                    height: NonZeroU32::new(72).unwrap(),
1910                })
1911                .with_bg_color(Color::Rgb(0x1E, 0x23, 0x26))
1912                .with_fg_color(Color::White)
1913                .build_headless_with_format(TextureFormat::Rgba8Unorm),
1914            )
1915            .unwrap(),
1916        )
1917        .unwrap();
1918
1919        terminal
1920            .draw(|f| {
1921                let block = Block::bordered();
1922                let area = block.inner(f.area());
1923                f.render_widget(block, f.area());
1924                f.render_widget(Paragraph::new("TEST"), area);
1925            })
1926            .unwrap();
1927
1928        let surface = &terminal.backend().surface;
1929        tex2buffer(
1930            &terminal.backend().device,
1931            &terminal.backend().queue,
1932            surface,
1933        );
1934        {
1935            let buffer = surface.buffer.as_ref().unwrap().slice(..);
1936
1937            let (send, recv) = oneshot::channel();
1938            buffer.map_async(wgpu::MapMode::Read, move |data| {
1939                send.send(data).unwrap();
1940            });
1941            terminal.backend().device.poll(wgpu::MaintainBase::Wait);
1942            recv.recv().unwrap().unwrap();
1943
1944            let data = buffer.get_mapped_range();
1945            let image =
1946                ImageBuffer::<Rgba<u8>, _>::from_raw(surface.width, surface.height, data).unwrap();
1947
1948            let pixels = image.pixels().copied().collect::<Vec<_>>();
1949            let golden = load_from_memory(include_bytes!("goldens/rgb_conversion.png")).unwrap();
1950            let golden_pixels = golden.pixels().map(|(_, _, px)| px).collect::<Vec<_>>();
1951
1952            assert!(
1953                pixels == golden_pixels,
1954                "Rendered image differs from golden"
1955            );
1956        }
1957        surface.buffer.as_ref().unwrap().unmap();
1958    }
1959
1960    #[test]
1961    #[serial]
1962    fn srgb_conversion() {
1963        let mut terminal = Terminal::new(
1964            futures_lite::future::block_on(
1965                Builder::<DefaultPostProcessor>::from_font(
1966                    Font::new(include_bytes!("fonts/Fairfax.ttf")).expect("Invalid font file"),
1967                )
1968                .with_width_and_height(Dimensions {
1969                    width: NonZeroU32::new(256).unwrap(),
1970                    height: NonZeroU32::new(72).unwrap(),
1971                })
1972                .with_bg_color(Color::Rgb(0x1E, 0x23, 0x26))
1973                .with_fg_color(Color::White)
1974                .build_headless_with_format(TextureFormat::Rgba8UnormSrgb),
1975            )
1976            .unwrap(),
1977        )
1978        .unwrap();
1979
1980        terminal
1981            .draw(|f| {
1982                let block = Block::bordered();
1983                let area = block.inner(f.area());
1984                f.render_widget(block, f.area());
1985                f.render_widget(Paragraph::new("TEST"), area);
1986            })
1987            .unwrap();
1988
1989        let surface = &terminal.backend().surface;
1990        tex2buffer(
1991            &terminal.backend().device,
1992            &terminal.backend().queue,
1993            surface,
1994        );
1995        {
1996            let buffer = surface.buffer.as_ref().unwrap().slice(..);
1997
1998            let (send, recv) = oneshot::channel();
1999            buffer.map_async(wgpu::MapMode::Read, move |data| {
2000                send.send(data).unwrap();
2001            });
2002            terminal.backend().device.poll(wgpu::MaintainBase::Wait);
2003            recv.recv().unwrap().unwrap();
2004
2005            let data = buffer.get_mapped_range();
2006            let image =
2007                ImageBuffer::<Rgba<u8>, _>::from_raw(surface.width, surface.height, data).unwrap();
2008
2009            let pixels = image.pixels().copied().collect::<Vec<_>>();
2010            let golden = load_from_memory(include_bytes!("goldens/srgb_conversion.png")).unwrap();
2011            let golden_pixels = golden.pixels().map(|(_, _, px)| px).collect::<Vec<_>>();
2012
2013            assert!(
2014                pixels == golden_pixels,
2015                "Rendered image differs from golden"
2016            );
2017        }
2018        surface.buffer.as_ref().unwrap().unmap();
2019    }
2020
2021    #[test]
2022    #[cfg(feature = "png")]
2023    fn png() {
2024        use crate::backend::wgpu_backend::extract_color_image;
2025        let golden = load_from_memory(include_bytes!("goldens/A.png")).unwrap();
2026        let raster = RasterGlyphImage {
2027            x: 0,
2028            y: 0,
2029            width: golden.width() as u16,
2030            height: golden.height() as u16,
2031            pixels_per_em: 0,
2032            format: RasterImageFormat::PNG,
2033            data: include_bytes!("goldens/A.png"),
2034        };
2035
2036        let mut image = vec![];
2037        let extracted = extract_color_image(
2038            &mut image,
2039            raster,
2040            Entry::Cached(CacheRect {
2041                x: 0,
2042                y: 0,
2043                width: golden.width(),
2044                height: golden.height(),
2045            }),
2046            1.0,
2047        )
2048        .expect("Didn't extract png")
2049        .1;
2050
2051        for (l, r) in bytemuck::cast_slice::<_, u8>(&extracted)
2052            .chunks(4)
2053            .zip(golden.pixels())
2054        {
2055            let [r, g, b, a] = r.2 .0;
2056            assert_eq!(l, [a, b, g, r]);
2057        }
2058    }
2059
2060    #[test]
2061    fn bgra() {
2062        use crate::backend::wgpu_backend::extract_color_image;
2063
2064        const BLUE: u8 = 2;
2065        const GREEN: u8 = 4;
2066        const RED: u8 = 8;
2067        const ALPHA: u8 = 127;
2068        let data = [BLUE, GREEN, RED, ALPHA];
2069        let raster = RasterGlyphImage {
2070            x: 0,
2071            y: 0,
2072            width: 1,
2073            height: 1,
2074            pixels_per_em: 0,
2075            format: RasterImageFormat::BitmapPremulBgra32,
2076            data: &data,
2077        };
2078
2079        let mut image = vec![];
2080        let extracted = extract_color_image(
2081            &mut image,
2082            raster,
2083            Entry::Cached(CacheRect {
2084                x: 0,
2085                y: 0,
2086                width: 1,
2087                height: 1,
2088            }),
2089            1.0,
2090        )
2091        .expect("Didn't extract bgra")
2092        .1;
2093
2094        assert_eq!(
2095            bytemuck::bytes_of(&extracted[0]),
2096            [RED * 2, GREEN * 2, BLUE * 2, ALPHA]
2097        );
2098    }
2099
2100    #[test]
2101    fn bmp1() {
2102        let data0 = 0b1000_0001;
2103        let data1 = 0b0001_1000;
2104        let raster = RasterGlyphImage {
2105            x: 0,
2106            y: 0,
2107            width: 4,
2108            height: 2,
2109            pixels_per_em: 0,
2110            format: RasterImageFormat::BitmapMono,
2111            data: &[data0, data1],
2112        };
2113
2114        let mut image = vec![];
2115        let extracted = extract_bw_image(
2116            &mut image,
2117            raster,
2118            Entry::Cached(CacheRect {
2119                x: 0,
2120                y: 0,
2121                width: 4,
2122                height: 2,
2123            }),
2124            1.0,
2125        )
2126        .expect("Didn't extract bmp1")
2127        .1;
2128
2129        assert_eq!(
2130            bytemuck::cast_slice::<_, u8>(&extracted),
2131            bytemuck::cast_slice(&[
2132                [
2133                    [255u8, 255, 255, 255,],
2134                    [255, 255, 255, 0,],
2135                    [255, 255, 255, 0,],
2136                    [255, 255, 255, 0,],
2137                ],
2138                [
2139                    [255, 255, 255, 0,],
2140                    [255, 255, 255, 0,],
2141                    [255, 255, 255, 0,],
2142                    [255, 255, 255, 255,],
2143                ],
2144            ])
2145        );
2146    }
2147
2148    #[test]
2149    fn bmp1_packed() {
2150        let data0 = 0b1000_0001;
2151        let data1 = 0b0001_1000;
2152        let raster = RasterGlyphImage {
2153            x: 0,
2154            y: 0,
2155            width: 8,
2156            height: 2,
2157            pixels_per_em: 0,
2158            format: RasterImageFormat::BitmapMonoPacked,
2159            data: &[data0, data1],
2160        };
2161
2162        let mut image = vec![];
2163        let extracted = extract_bw_image(
2164            &mut image,
2165            raster,
2166            Entry::Cached(CacheRect {
2167                x: 0,
2168                y: 0,
2169                width: 8,
2170                height: 2,
2171            }),
2172            1.0,
2173        )
2174        .expect("Didn't extract bmp1")
2175        .1;
2176
2177        assert_eq!(
2178            bytemuck::cast_slice::<_, u8>(&extracted),
2179            bytemuck::cast_slice(&[
2180                [
2181                    [255u8, 255, 255, 255,],
2182                    [255, 255, 255, 0,],
2183                    [255, 255, 255, 0,],
2184                    [255, 255, 255, 0,],
2185                    [255, 255, 255, 0,],
2186                    [255, 255, 255, 0,],
2187                    [255, 255, 255, 0,],
2188                    [255, 255, 255, 255,],
2189                ],
2190                [
2191                    [255, 255, 255, 0,],
2192                    [255, 255, 255, 0,],
2193                    [255, 255, 255, 0,],
2194                    [255, 255, 255, 255,],
2195                    [255, 255, 255, 255,],
2196                    [255, 255, 255, 0,],
2197                    [255, 255, 255, 0,],
2198                    [255, 255, 255, 0,],
2199                ],
2200            ])
2201        );
2202    }
2203
2204    #[test]
2205    fn bmp2() {
2206        let data0 = 0b1010_1101u8.reverse_bits();
2207        let data1 = 0b0000_1000u8.reverse_bits();
2208        let data2 = 0b0111_1010u8.reverse_bits();
2209        let data3 = 0b0000_1000u8.reverse_bits();
2210        let raster = RasterGlyphImage {
2211            x: 0,
2212            y: 0,
2213            width: 6,
2214            height: 2,
2215            pixels_per_em: 0,
2216            format: RasterImageFormat::BitmapGray2,
2217            data: &[data0, data1, data2, data3],
2218        };
2219
2220        let mut image = vec![];
2221        let extracted = extract_bw_image(
2222            &mut image,
2223            raster,
2224            Entry::Cached(CacheRect {
2225                x: 0,
2226                y: 0,
2227                width: 6,
2228                height: 2,
2229            }),
2230            1.0,
2231        )
2232        .expect("Didn't extract bmp2")
2233        .1;
2234
2235        assert_eq!(
2236            bytemuck::cast_slice::<_, u8>(&extracted),
2237            bytemuck::cast_slice(&[
2238                [
2239                    [255, 255, 255, LUT_2[0b10],],
2240                    [255, 255, 255, LUT_2[0b10],],
2241                    [255, 255, 255, LUT_2[0b11],],
2242                    [255, 255, 255, LUT_2[0b01],],
2243                    [255, 255, 255, LUT_2[0b00],],
2244                    [255, 255, 255, LUT_2[0b00],],
2245                ],
2246                [
2247                    [255, 255, 255, LUT_2[0b01],],
2248                    [255, 255, 255, LUT_2[0b11],],
2249                    [255, 255, 255, LUT_2[0b10],],
2250                    [255, 255, 255, LUT_2[0b10],],
2251                    [255, 255, 255, LUT_2[0b00],],
2252                    [255, 255, 255, LUT_2[0b00],],
2253                ],
2254            ])
2255        );
2256    }
2257
2258    #[test]
2259    fn bmp2_packed() {
2260        let data0 = 0b1010_1101u8.reverse_bits();
2261        let data1 = 0b0000_1000u8.reverse_bits();
2262        let data2 = 0b0111_1010u8.reverse_bits();
2263        let data3 = 0b0000_1000u8.reverse_bits();
2264        let data4 = 0b0000_1000u8.reverse_bits();
2265        let data5 = 0b0000_1000u8.reverse_bits();
2266        let raster = RasterGlyphImage {
2267            x: 0,
2268            y: 0,
2269            width: 6,
2270            height: 4,
2271            pixels_per_em: 0,
2272            format: RasterImageFormat::BitmapGray2Packed,
2273            data: &[data0, data1, data2, data3, data4, data5],
2274        };
2275
2276        let mut image = vec![];
2277        let extracted = extract_bw_image(
2278            &mut image,
2279            raster,
2280            Entry::Cached(CacheRect {
2281                x: 0,
2282                y: 0,
2283                width: 6,
2284                height: 4,
2285            }),
2286            1.0,
2287        )
2288        .expect("Didn't extract bmp2")
2289        .1;
2290
2291        assert_eq!(
2292            bytemuck::cast_slice::<_, u8>(&extracted),
2293            bytemuck::cast_slice(&[
2294                [
2295                    [255, 255, 255, LUT_2[0b10],],
2296                    [255, 255, 255, LUT_2[0b10],],
2297                    [255, 255, 255, LUT_2[0b11],],
2298                    [255, 255, 255, LUT_2[0b01],],
2299                    [255, 255, 255, LUT_2[0b00],],
2300                    [255, 255, 255, LUT_2[0b00],],
2301                ],
2302                [
2303                    [255, 255, 255, LUT_2[0b10],],
2304                    [255, 255, 255, LUT_2[0b00],],
2305                    [255, 255, 255, LUT_2[0b01],],
2306                    [255, 255, 255, LUT_2[0b11],],
2307                    [255, 255, 255, LUT_2[0b10],],
2308                    [255, 255, 255, LUT_2[0b10],],
2309                ],
2310                [
2311                    [255, 255, 255, LUT_2[0b00],],
2312                    [255, 255, 255, LUT_2[0b00],],
2313                    [255, 255, 255, LUT_2[0b10],],
2314                    [255, 255, 255, LUT_2[0b00],],
2315                    [255, 255, 255, LUT_2[0b00],],
2316                    [255, 255, 255, LUT_2[0b00],],
2317                ],
2318                [
2319                    [255, 255, 255, LUT_2[0b10],],
2320                    [255, 255, 255, LUT_2[0b00],],
2321                    [255, 255, 255, LUT_2[0b00],],
2322                    [255, 255, 255, LUT_2[0b00],],
2323                    [255, 255, 255, LUT_2[0b10],],
2324                    [255, 255, 255, LUT_2[0b00],],
2325                ]
2326            ])
2327        );
2328    }
2329
2330    #[test]
2331    fn bmp4() {
2332        let data0 = 0b1010_1000u8.reverse_bits();
2333        let data1 = 0b0000_1000u8.reverse_bits();
2334        let raster = RasterGlyphImage {
2335            x: 0,
2336            y: 0,
2337            width: 1,
2338            height: 2,
2339            pixels_per_em: 0,
2340            format: RasterImageFormat::BitmapGray4,
2341            data: &[data0, data1],
2342        };
2343
2344        let mut image = vec![];
2345        let extracted = extract_bw_image(
2346            &mut image,
2347            raster,
2348            Entry::Cached(CacheRect {
2349                x: 0,
2350                y: 0,
2351                width: 1,
2352                height: 2,
2353            }),
2354            1.0,
2355        )
2356        .expect("Didn't extract bmp4")
2357        .1;
2358
2359        assert_eq!(
2360            bytemuck::cast_slice::<_, u8>(&extracted),
2361            bytemuck::cast_slice(&[
2362                [[255, 255, 255, LUT_4[0b1010],],],
2363                [[255, 255, 255, LUT_4[0b0000],],],
2364            ])
2365        );
2366    }
2367
2368    #[test]
2369    fn bmp4_packed() {
2370        let data0 = 0b1111_0001u8.reverse_bits();
2371        let data1 = 0b0011_1100u8.reverse_bits();
2372        let raster = RasterGlyphImage {
2373            x: 0,
2374            y: 0,
2375            width: 2,
2376            height: 2,
2377            pixels_per_em: 0,
2378            format: RasterImageFormat::BitmapGray4Packed,
2379            data: &[data0, data1],
2380        };
2381
2382        let mut image = vec![];
2383        let extracted = extract_bw_image(
2384            &mut image,
2385            raster,
2386            Entry::Cached(CacheRect {
2387                x: 0,
2388                y: 0,
2389                width: 2,
2390                height: 2,
2391            }),
2392            1.0,
2393        )
2394        .expect("Didn't extract bmp4")
2395        .1;
2396
2397        assert_eq!(
2398            bytemuck::cast_slice::<_, u8>(&extracted),
2399            bytemuck::cast_slice(&[
2400                [
2401                    [255, 255, 255, LUT_4[0b1111],],
2402                    [255, 255, 255, LUT_4[0b0001],],
2403                ],
2404                [
2405                    [255, 255, 255, LUT_4[0b0011],],
2406                    [255, 255, 255, LUT_4[0b1100],],
2407                ],
2408            ])
2409        );
2410    }
2411}