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