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}
132type Rendered = IndexMap<(i32, i32, GlyphId), RenderInfo, RandomState>;
136
137type Sourced = HashSet<(i32, i32, GlyphId, u32), RandomState>;
139
140pub 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 pub fn post_processor(&self) -> &P {
210 &self.post_process
211 }
212
213 pub fn post_processor_mut(&mut self) -> &mut P {
216 &mut self.post_process
217 }
218
219 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 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 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 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 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 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 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, index_offset + 1, index_offset + 2, index_offset + 2, index_offset + 3, index_offset + 1, ]);
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 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 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 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 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 1.0,
1012 0.0,
1013 -0.25,
1014 1.0,
1015 -0.25 * cached.width as f32,
1016 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 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}