dos_like/video.rs
1//! Module for video related operations,
2//! including graphics and text output.
3
4use std::{
5 ffi::{CStr, CString},
6 num::NonZeroU32,
7 os::raw::{c_int, c_uint},
8 ptr::NonNull,
9};
10
11use crate::FileError;
12
13/// A simple descriptor for whether a video mode is in text or graphics mode.
14#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
15pub enum VideoModeKind {
16 /// Text mode, where each cell is a character or glyph.
17 /// and the resolution is defined via cell matrix and font size.
18 Text,
19 /// Graphics mode, where the resolution is specified directly
20 /// and the graphics are drawn to the screen in indexed color mode.
21 Graphics,
22}
23
24/// A video mode.
25///
26/// This type maps to the `videomode_t` struct in the original framework.
27/// Each variant is either in text mode or graphics mode.
28#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
29#[repr(u32)]
30pub enum VideoMode {
31 /// Text mode, 40 columns and 25 rows, 8x8 font size.
32 Text40x25_8x8 = dos_like_sys::videomode_t_videomode_40x25_8x8,
33 /// Text mode, 40 columns and 25 rows, 9x16 font size.
34 Text40x25_9x16 = dos_like_sys::videomode_t_videomode_40x25_9x16,
35 /// Text mode, 80 columns and 25 rows, 8x8 font size.
36 Text80x25_8x8 = dos_like_sys::videomode_t_videomode_80x25_8x8,
37 /// Text mode, 80 columns and 25 rows, 8x16 font size.
38 ///
39 /// This is the mode set by default.
40 Text80x25_8x16 = dos_like_sys::videomode_t_videomode_80x25_8x16,
41 /// Text mode, 80 columns and 25 rows, 9x16 font size.
42 Text80x25_9x16 = dos_like_sys::videomode_t_videomode_80x25_9x16,
43 /// Text mode, 80 columns and 43 rows, 8x8 font size.
44 Text80x43_8x8 = dos_like_sys::videomode_t_videomode_80x43_8x8,
45 /// Text mode, 80 columns and 50 rows, 8x8 font size.
46 Text80x50_8x8 = dos_like_sys::videomode_t_videomode_80x50_8x8,
47 /// Graphics mode, 320x200 pixels.
48 Graphics320x200 = dos_like_sys::videomode_t_videomode_320x200,
49 /// Graphics mode, 320x240 pixels.
50 Graphics320x240 = dos_like_sys::videomode_t_videomode_320x240,
51 /// Graphics mode, 320x400 pixels.
52 Graphics320x400 = dos_like_sys::videomode_t_videomode_320x400,
53 /// Graphics mode, 640x200 pixels.
54 Graphics640x200 = dos_like_sys::videomode_t_videomode_640x200,
55 /// Graphics mode, 640x350 pixels.
56 Graphics640x350 = dos_like_sys::videomode_t_videomode_640x350,
57 /// Graphics mode, 640x400 pixels.
58 Graphics640x400 = dos_like_sys::videomode_t_videomode_640x400,
59 /// Graphics mode, 640x480 pixels.
60 Graphics640x480 = dos_like_sys::videomode_t_videomode_640x480,
61}
62
63impl VideoMode {
64 /// Gets the kind of video mode this is (text or graphics).
65 pub fn kind(self) -> VideoModeKind {
66 match self {
67 VideoMode::Text40x25_8x8
68 | VideoMode::Text40x25_9x16
69 | VideoMode::Text80x25_8x8
70 | VideoMode::Text80x25_8x16
71 | VideoMode::Text80x25_9x16
72 | VideoMode::Text80x43_8x8
73 | VideoMode::Text80x50_8x8 => VideoModeKind::Text,
74 VideoMode::Graphics320x200
75 | VideoMode::Graphics320x240
76 | VideoMode::Graphics320x400
77 | VideoMode::Graphics640x200
78 | VideoMode::Graphics640x350
79 | VideoMode::Graphics640x400
80 | VideoMode::Graphics640x480 => VideoModeKind::Graphics,
81 }
82 }
83
84 /// Gets whether the given video mode is in graphics mode.
85 #[inline]
86 pub fn is_graphics(self) -> bool {
87 self.kind() == VideoModeKind::Graphics
88 }
89
90 /// Gets whether the given video mode is in text mode.
91 #[inline]
92 pub fn is_text(self) -> bool {
93 self.kind() == VideoModeKind::Text
94 }
95
96 /// Sets the application video mode to this one.
97 ///
98 /// Equivalent to the module's [`set_video_mode`].
99 #[inline]
100 pub fn set_video_mode(self) {
101 set_video_mode(self)
102 }
103}
104
105/// Sets the video mode.
106#[inline]
107pub fn set_video_mode(mode: VideoMode) {
108 unsafe {
109 dos_like_sys::setvideomode(mode as c_uint);
110 }
111}
112
113/// Enables or disables screen double buffering.
114#[inline]
115pub fn set_double_buffer(enabled: bool) {
116 unsafe {
117 dos_like_sys::setdoublebuffer(enabled as c_int);
118 }
119}
120
121/// Obtains the screen width in pixels.
122#[inline]
123pub fn screen_width() -> u16 {
124 unsafe { dos_like_sys::screenwidth() as u16 }
125}
126
127/// Obtains the screen height in pixels.
128#[inline]
129pub fn screen_height() -> u16 {
130 unsafe { dos_like_sys::screenheight() as u16 }
131}
132
133/// Sets a palette color by index.
134#[inline]
135pub fn set_pal(index: usize, r: u8, g: u8, b: u8) {
136 unsafe {
137 dos_like_sys::setpal(index as c_int, r as c_int, g as c_int, b as c_int);
138 }
139}
140
141/// Gets a palette color by index.
142#[inline]
143pub fn pal(index: usize) -> (u8, u8, u8) {
144 let (mut r, mut g, mut b) = (0, 0, 0);
145 unsafe {
146 dos_like_sys::getpal(index as c_int, &mut r, &mut g, &mut b);
147 (r as u8, g as u8, b as u8)
148 }
149}
150
151// -- Graphics buffer manipulation functions
152// Due to the way the original framework works,
153// some operations are hard to be marked as safe by the compiler.
154
155/// Gets a mutable slice of the current screen buffer.
156///
157/// Only makes sense in graphics mode.
158/// The length of the slice is equal to the number of pixels on the screen.
159///
160/// # Safety
161///
162/// It is not guaranteed by the compiler
163/// that the access to the screen buffer is exclusive.
164/// Calling any function in this module that draws _anything_ to the screen
165/// during the returned slice's lifetime
166/// is _undefined behavior_.
167/// It is also undefined behavior to call this function multiple times
168/// without dropping the previous slices first.
169///
170/// However, if _double buffering_ is enabled
171/// (via [`set_double_buffer`]),
172/// then it is safe to call [`swap_buffers`] or [`swap_buffers_and_get`]
173/// and immediately drop this slice in favor of the new buffer slice.
174pub unsafe fn screen_buffer() -> &'static mut [u8] {
175 // Safety: it is documented that the user
176 // must not draw anything through other functions,
177 // so that buffer access is truly exclusive.
178 #[allow(unused_unsafe)]
179 unsafe {
180 let buf = dos_like_sys::screenbuffer();
181 let width = dos_like_sys::screenwidth() as usize;
182 let height = dos_like_sys::screenheight() as usize;
183 std::slice::from_raw_parts_mut(buf, width * height)
184 }
185}
186
187/// Swaps the current screen buffers
188/// and returns a mutable slice of the current screen buffer.
189///
190/// Only makes sense in graphics mode.
191///
192/// # Safety
193///
194/// It is not guaranteed by the compiler
195/// that the access to the screen buffer is exclusive.
196/// Calling any function in this module that draws _anything_ to the screen
197/// during the returned slice's lifetime
198/// is _undefined behavior_.
199///
200/// Moreover, if _double buffering_ is disabled,
201/// any buffer slice obtained through this function or [`screen_buffer`]
202/// must be dropped _before_ this call.
203/// That is,
204/// it is only safe to call [`swap_buffers`] or `swap_buffers_and_get`
205/// with an existing buffer slice
206/// if double buffering is enabled
207/// (via [`set_double_buffer`]).
208/// and the other slice is immediately dropped afterwards.
209///
210/// # Example
211///
212/// ```no_run
213/// # use dos_like::*;
214/// set_video_mode(VideoMode::Graphics320x200);
215/// set_double_buffer(true);
216/// // safety: I solemnly swear that I will not draw anything through other functions
217/// let mut buffer = unsafe { screen_buffer() };
218///
219/// loop {
220/// wait_vbl();
221/// // do things with buffer
222/// for (i, v) in buffer.chunks_mut(320).enumerate() {
223/// v.fill(i as u8);
224/// }
225///
226/// // safety: previous buffer slice will be dropped
227/// buffer = unsafe { swap_buffers_and_get() };
228/// }
229/// ```
230pub unsafe fn swap_buffers_and_get() -> &'static mut [u8] {
231 // Safety: it is documented that the user
232 // must drop other buffer slices
233 // and must not draw anything through other functions,
234 // so that buffer access is truly exclusive and not aliased.
235 #[allow(unused_unsafe)]
236 unsafe {
237 let buf = dos_like_sys::swapbuffers();
238 let width = dos_like_sys::screenwidth() as usize;
239 let height = dos_like_sys::screenheight() as usize;
240 std::slice::from_raw_parts_mut(buf, width * height)
241 }
242}
243
244/// Swaps the current screen buffers,
245/// thus changing what to draw to the screen.
246///
247/// When double buffering is enabled,
248/// any drawing operations are performed on the off-screen buffer,
249/// so this function needs to be called
250/// for the changes to be displayed.
251///
252/// Only makes sense in graphics mode.
253///
254/// For the function which also returns a slice,
255/// see [`swap_buffers_and_get`].
256///
257/// # Example
258///
259/// ```no_run
260/// # use dos_like::*;
261/// set_video_mode(VideoMode::Graphics320x200);
262/// // enable double buffering
263/// set_double_buffer(true);
264///
265/// let mut i = 0;
266/// loop {
267/// // draw some things
268/// line(0, 0, i, 200);
269/// i += 1;
270///
271/// // swap!
272/// swap_buffers();
273/// }
274/// ```
275pub fn swap_buffers() {
276 // safety: no slice is returned,
277 // so aliased slices are not possible without calling
278 // the other unsafe functions
279 unsafe {
280 dos_like_sys::swapbuffers();
281 }
282}
283
284// -- Graphics manipulation functions
285
286/// Blits a rectangular portion of a video data buffer to the screen.
287///
288/// - `x` and `y` are the target coordinates of the top-left corner
289/// to blit on the screen
290/// - `width` and `height` are the full dimensions of the source data
291/// - `src_x` and `src_y` are the coordinates of the starting position
292/// to blit from the source data
293/// - `src_width` and `src_height` are the width and height to effectively blit
294/// from the source data
295///
296/// # Panic
297///
298/// Panics if the given source parameters
299/// are incompatible with the length of the source,
300/// since this is likely a bug.
301pub fn blit(
302 x: i32,
303 y: i32,
304 source: &[u8],
305 width: u16,
306 height: u16,
307 src_x: u16,
308 src_y: u16,
309 src_width: u16,
310 src_height: u16,
311) {
312 if width as usize * height as usize > source.len() {
313 panic!(
314 "blit: source data ({} bytes) is too short for resolution {}x{}",
315 source.len(),
316 width,
317 height
318 );
319 }
320
321 // Safety:
322 // - the source data length has been validated against `width` and `height`
323 // - although a *mut pointer is passed, the impl is sure to never write to it
324 unsafe {
325 dos_like_sys::blit(
326 x as c_int,
327 y as c_int,
328 source.as_ptr() as *mut _,
329 width as c_int,
330 height as c_int,
331 src_x as c_int,
332 src_y as c_int,
333 src_width as c_int,
334 src_height as c_int,
335 );
336 }
337}
338
339/// Blits a masked rectangular portion of a video data buffer to the screen,
340/// skipping pixels encoded as fully transparent.
341///
342/// - `x` and `y` are the target coordinates of the top-left corner
343/// to blit on the screen
344/// - `width` and `height` are the full dimensions of the source data
345/// - `src_x` and `src_y` are the coordinates of the starting position
346/// to blit from the source data
347/// - `src_width` and `src_height` are the width and height to effectively blit
348/// from the source data
349/// - `color_key` is the color to skip when blitting
350///
351/// # Panic
352///
353/// Panics if the given source parameters
354/// are incompatible with the length of the source,
355/// since this is likely a bug.
356pub fn mask_blit(
357 x: i32,
358 y: i32,
359 source: &[u8],
360 width: u16,
361 height: u16,
362 src_x: u16,
363 src_y: u16,
364 src_width: u16,
365 src_height: u16,
366 color_key: u8,
367) {
368 if width as usize * height as usize > source.len() {
369 panic!(
370 "blit: source data ({} bytes) is too short for resolution {}x{}",
371 source.len(),
372 width,
373 height
374 );
375 }
376
377 // Safety:
378 // - the source data length has been validated against `width` and `height`
379 // - although a *mut pointer is passed, the impl is sure to never write to it
380 unsafe {
381 dos_like_sys::maskblit(
382 x as c_int,
383 y as c_int,
384 source.as_ptr() as *mut _,
385 width as c_int,
386 height as c_int,
387 src_x as c_int,
388 src_y as c_int,
389 src_width as c_int,
390 src_height as c_int,
391 color_key as c_int,
392 );
393 }
394}
395
396//void blit( int x, int y, unsigned char* source, int width, int height, int srcx, int srcy, int srcw, int srch );
397//void maskblit( int x, int y, unsigned char* source, int width, int height, int srcx, int srcy, int srcw, int srch, int colorkey );
398
399/// Clears the screen when in graphics mode.
400#[inline]
401pub fn clear_screen() {
402 unsafe {
403 dos_like_sys::clearscreen();
404 }
405}
406
407/// Gets the color of a single pixel on the screen.
408///
409/// Only makes sense in graphics mode.
410#[inline]
411pub fn pixel(x: i32, y: i32) -> u8 {
412 unsafe { dos_like_sys::getpixel(x as c_int, y as c_int) as u8 }
413}
414
415/// Puts a color on a single pixel.
416///
417/// Only makes sense in graphics mode.
418#[inline]
419pub fn put_pixel(x: u16, y: u16, color: u8) {
420 unsafe {
421 dos_like_sys::putpixel(x as c_int, y as c_int, color as c_int);
422 }
423}
424
425/// Draws a horizonal line.
426///
427/// Only makes sense in graphics mode.
428#[inline]
429pub fn h_line(x: i32, y: i32, len: u16, color: u8) {
430 unsafe {
431 dos_like_sys::hline(x as c_int, y as c_int, len as c_int, color as c_int);
432 }
433}
434
435/// Sets the foreground color to the given palette color index
436/// for subsequent drawing operations.
437///
438/// Only works in graphics mode.
439#[inline]
440pub fn set_color(color: u8) {
441 unsafe {
442 dos_like_sys::setcolor(color as c_int);
443 }
444}
445
446/// Gets the current foreground color by palette color index.
447///
448/// Returns 0 if video is not in graphics mode.
449#[inline]
450pub fn get_color() -> u8 {
451 unsafe { dos_like_sys::getcolor() as u8 }
452}
453
454/// Draws a line on the screen from one position to another.
455///
456/// Only makes sense in graphics mode.
457#[inline]
458pub fn line(x1: i32, y1: i32, x2: i32, y2: i32) {
459 unsafe {
460 dos_like_sys::line(x1 as c_int, y1 as c_int, x2 as c_int, y2 as c_int);
461 }
462}
463
464/// Draws a non-filled rectangle on the screen.
465///
466/// Only makes sense in graphics mode.
467#[inline]
468pub fn rectangle(x1: i32, y1: i32, width: u16, height: u16) {
469 unsafe {
470 dos_like_sys::rectangle(x1 as c_int, y1 as c_int, width as c_int, height as c_int);
471 }
472}
473
474/// Draws a filled rectangle on the screen.
475///
476/// Only makes sense in graphics mode.
477#[inline]
478pub fn bar(x1: i32, y1: i32, width: u16, height: u16) {
479 unsafe {
480 dos_like_sys::bar(x1 as c_int, y1 as c_int, width as c_int, height as c_int);
481 }
482}
483
484/// Draws a circle with no filling on the screen.
485///
486/// Only makes sense in graphics mode.
487#[inline]
488pub fn circle(x: i32, y: i32, r: u16) {
489 unsafe {
490 dos_like_sys::circle(x as c_int, y as c_int, r as c_int);
491 }
492}
493
494/// Draws a filled circle on the screen.
495///
496/// Only makes sense in graphics mode.
497#[inline]
498pub fn fill_circle(x: i32, y: i32, r: u16) {
499 unsafe {
500 dos_like_sys::fillcircle(x as c_int, y as c_int, r as c_int);
501 }
502}
503
504/// Draws a non-filled ellipse on the screen.
505///
506/// Only makes sense in graphics mode.
507#[inline]
508pub fn ellipse(x: i32, y: i32, rx: u16, ry: u16) {
509 unsafe {
510 dos_like_sys::ellipse(x as c_int, y as c_int, rx as c_int, ry as c_int);
511 }
512}
513
514/// Draws a filled ellipse on the screen.
515///
516/// Only makes sense in graphics mode.
517#[inline]
518pub fn fill_ellipse(x: i32, y: i32, rx: u16, ry: u16) {
519 unsafe {
520 dos_like_sys::fillellipse(x as c_int, y as c_int, rx as c_int, ry as c_int);
521 }
522}
523
524/// Draws a poly-line on the screen,
525/// with the given flat list of XY coordinates in pixels.
526///
527/// Only makes sense in graphics mode.
528///
529/// # Panic
530///
531/// Panics if the given list of points is empty or not even.
532#[inline]
533pub fn draw_poly(points: &[i32]) {
534 assert!(!points.is_empty() && points.len() % 2 == 0);
535
536 // Safety: although the pointer type is *mut,
537 // it never really writes via the pointer.
538 unsafe {
539 dos_like_sys::drawpoly(points.as_ptr() as *mut _, (points.len() / 2) as c_int);
540 }
541}
542
543/// Draws a filled polygon on the screen,
544/// with the given flat list of XY coordinates in pixels.
545///
546/// Only makes sense in graphics mode.
547///
548/// # Panic
549///
550/// Panics if the given list of points is empty or not even.
551#[inline]
552pub fn fill_poly(points: &[i32]) {
553 assert!(!points.is_empty() && points.len() % 2 == 0);
554
555 // Safety: although the pointer type is *mut,
556 // it never really writes via the pointer.
557 unsafe {
558 dos_like_sys::fillpoly(points.as_ptr() as *mut _, (points.len() / 2) as c_int);
559 }
560}
561
562/// Flood fills the screen from the given position.
563///
564/// Only makes sense in graphics mode.
565pub fn flood_fill(x: i32, y: i32) {
566 unsafe {
567 dos_like_sys::floodfill(x as c_int, y as c_int);
568 }
569}
570
571/// Flood fills the screen from the given position
572/// with the given color as boundary.
573///
574/// Only makes sense in graphics mode.
575pub fn boundary_fill(x: i32, y: i32, boundary: u8) {
576 unsafe {
577 dos_like_sys::boundaryfill(x as c_int, y as c_int, boundary as c_int);
578 }
579}
580
581/// Blits a text to the screen at the given position.
582///
583/// XY coordinates are in pixels.
584///
585/// Only makes sense in graphics mode.
586pub fn out_text_xy(x: i32, y: i32, text: impl AsRef<[u8]>) {
587 let text = CString::new(text.as_ref()).unwrap();
588
589 unsafe {
590 dos_like_sys::outtextxy(x as c_int, y as c_int, text.as_ptr() as *const _);
591 }
592}
593
594/// Blits a text to the screen at the given position,
595/// wrapping around before it goes beyond the width specified.
596///
597/// XY coordinates and width are in pixels.
598///
599/// Only makes sense in graphics mode.
600pub fn wrap_text_xy(x: i32, y: i32, text: impl AsRef<[u8]>, width: u16) {
601 let text = CString::new(text.as_ref()).unwrap();
602
603 unsafe {
604 dos_like_sys::wraptextxy(
605 x as c_int,
606 y as c_int,
607 text.as_ptr() as *const _,
608 width as c_int,
609 );
610 }
611}
612
613/// Blits a text to the screen centered at the given position.
614///
615/// XY coordinates and width are in pixels.
616///
617/// Only makes sense in graphics mode.
618pub fn center_text_xy(x: i32, y: i32, text: impl AsRef<[u8]>, width: u16) {
619 let text = CString::new(text.as_ref()).unwrap();
620
621 unsafe {
622 dos_like_sys::centertextxy(
623 x as c_int,
624 y as c_int,
625 text.as_ptr() as *const _,
626 width as c_int,
627 );
628 }
629}
630
631// -- Image reading
632
633/// An image loaded from a file.
634#[derive(Debug)]
635pub struct Image {
636 /// The color palette of the image.
637 palette: [u8; 768],
638 /// The real size of the palette
639 palette_count: u32,
640 /// The width of the image.
641 width: u32,
642 /// The height of the image.
643 height: u32,
644 /// Pointer to the indexed pixel data.
645 data: NonNull<u8>,
646}
647
648unsafe impl Send for Image {}
649unsafe impl Sync for Image {}
650
651impl Image {
652 pub fn width(&self) -> u32 {
653 self.width
654 }
655
656 pub fn height(&self) -> u32 {
657 self.height
658 }
659
660 /// Gets the image data as a slice of bytes,
661 /// each byte representing a pixel indexed by the image's palette.
662 pub fn data(&self) -> &[u8] {
663 unsafe {
664 std::slice::from_raw_parts(
665 self.data.as_ptr(),
666 self.width as usize * self.height as usize,
667 )
668 }
669 }
670
671 /// Gets the image data as a mutable slice of bytes,
672 /// each byte representing a pixel indexed by the image's palette.
673 pub fn data_mut(&mut self) -> &mut [u8] {
674 unsafe {
675 std::slice::from_raw_parts_mut(
676 self.data.as_ptr(),
677 self.width as usize * self.height as usize,
678 )
679 }
680 }
681
682 /// Gets the number of pixel colors in the palette.
683 /// The full size of the palette is `palette_count * 3` bytes.
684 pub fn palette_count(&self) -> u32 {
685 self.palette_count
686 }
687
688 /// Gets the image's color palette as a slice of bytes, in RGB
689 /// (8 bits per channel).
690 pub fn palette(&self) -> &[u8] {
691 &self.palette[..self.palette_count as usize * 3]
692 }
693
694 /// Gets the image's color palette as a mutable slice of bytes, in RGB
695 /// (8 bits per channel).
696 pub fn palette_mut(&mut self) -> &mut [u8] {
697 &mut self.palette[..self.palette_count as usize]
698 }
699
700 /// Gets the image's color palette as a reference to the underlying array,
701 /// in RGB (8 bits per channel).
702 ///
703 /// The maximum expected size of any palette is 768 bytes
704 /// (256 colors * 3 bytes per color).
705 /// Note that the real palette used might be smaller than
706 /// the full size of the array,
707 /// see [`palette`][self::Image::palette] for an accurate slice.
708 pub fn raw_palette(&self) -> &[u8; 768] {
709 &self.palette
710 }
711}
712
713/// Loads an image from a GIF file.
714pub fn load_gif(path: impl AsRef<str>) -> Result<Image, FileError> {
715 let filename = CString::new(path.as_ref()).map_err(|_| FileError::BadFilePath)?;
716 let mut width = 0;
717 let mut height = 0;
718 let mut palcount = 0;
719 let mut palette = [0; 768];
720
721 unsafe {
722 let data = dos_like_sys::loadgif(
723 filename.as_ptr(),
724 &mut width,
725 &mut height,
726 &mut palcount,
727 palette.as_mut_ptr(),
728 );
729
730 if let Some(data) = NonNull::new(data) {
731 Ok(Image {
732 width: width as u32,
733 height: height as u32,
734 palette_count: palcount as u32,
735 palette,
736 data,
737 })
738 } else {
739 Err(FileError::FileNotFound)
740 }
741 }
742}
743
744// -- Font manipulation functions --
745
746/// A font identifier.
747///
748/// Use [`install_user_font`] to obtain a font,
749/// or take one of the associated constants for the default fonts.
750#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
751#[repr(transparent)]
752pub struct Font(NonZeroU32);
753
754impl Font {
755 /// The default 8x8 font.
756 pub const DEFAULT_8X8: Font = Font(
757 // safety: definitely not 0
758 unsafe { NonZeroU32::new_unchecked(dos_like_sys::DEFAULT_FONT_8X8) },
759 );
760
761 /// The default 8x16 font.
762 pub const DEFAULT_8X16: Font = Font(
763 // safety: definitely not 0
764 unsafe { NonZeroU32::new_unchecked(dos_like_sys::DEFAULT_FONT_8X16) },
765 );
766
767 /// The default 9x16 font.
768 pub const DEFAULT_9X16: Font = Font(
769 // safety: definitely not 0
770 unsafe { NonZeroU32::new_unchecked(dos_like_sys::DEFAULT_FONT_9X16) },
771 );
772
773 /// Installs a font from a .fnt file
774 ///
775 /// This is the same as the module's [`install_user_font`] function.
776 pub fn install_user_font(filename: impl AsRef<str>) -> Result<Self, FileError> {
777 install_user_font(filename)
778 }
779
780 /// Obtains a font identifier by its internal index.
781 ///
782 /// This operation does not check whether the font really exists.
783 #[inline]
784 pub fn from_id(id: u32) -> Option<Font> {
785 Self::from_raw_id(id as c_int)
786 }
787
788 #[inline]
789 fn from_raw_id(id: c_int) -> Option<Font> {
790 NonZeroU32::new(id as u32).map(Font)
791 }
792
793 #[inline]
794 fn to_id(self) -> c_int {
795 self.0.get() as c_int
796 }
797}
798
799/// Installs a font from a .fnt file.
800///
801/// Returns the identifier of the font.
802pub fn install_user_font(filename: impl AsRef<str>) -> Result<Font, FileError> {
803 let filename = CString::new(filename.as_ref()).map_err(|_| FileError::BadFilePath)?;
804
805 unsafe {
806 let font_id = dos_like_sys::installuserfont(filename.as_ptr() as *const _);
807
808 Font::from_id(font_id as u32).ok_or(FileError::FileNotFound)
809 }
810}
811
812/// Sets the font and style of upcoming text blit operations.
813///
814/// This is only available in graphics mode with a font loaded.
815/// The operations is ignored if `FontId` does not correspond to a valid font.
816#[inline]
817pub fn set_text_style(font: Font, bold: bool, italic: bool, underline: bool) {
818 unsafe {
819 dos_like_sys::settextstyle(
820 font.to_id(),
821 bold as c_int,
822 italic as c_int,
823 underline as c_int,
824 );
825 }
826}
827
828// --- Pure text mode functions ---
829
830/// Writes a string to the screen, at the current cursor position.
831///
832/// Does nothing unless the video is in text mode.
833///
834/// This is equivalent to creating a [`CString`](std::ffi::CString)
835/// (so that it is null terminated)
836/// and calling [`put_cstr`].
837///
838/// # Panics
839///
840/// Panics if the given string cannot be converted to be printed to the screen.
841/// Always check for null characters (`\0`) in the string
842/// before calling this function.
843#[inline]
844pub fn put_str(string: impl AsRef<str>) {
845 let text = CString::new(string.as_ref()).unwrap();
846 put_cstr(&text)
847}
848
849/// Writes a C string to the screen, at the current cursor position.
850///
851/// Requires a valid, null terminated C-style string,
852/// but does not require a new string to be allocated.
853///
854/// Does nothing unless the video is in text mode.
855#[inline]
856pub fn put_cstr(text: impl AsRef<CStr>) {
857 unsafe {
858 dos_like_sys::cputs(text.as_ref().as_ptr() as *const _);
859 }
860}
861
862/// Sets the color of the text.
863///
864/// Only works in text mode.
865#[inline]
866pub fn text_color(color: u32) {
867 unsafe {
868 dos_like_sys::textcolor(color as c_int);
869 }
870}
871
872/// Sets the background color of the text by palette color index.
873///
874/// Only works in text mode.
875#[inline]
876pub fn text_background(color: u8) {
877 unsafe {
878 dos_like_sys::textbackground(color as c_int);
879 }
880}
881
882/// Moves the cursor to the specified position.
883///
884/// Only works in text mode.
885#[inline]
886pub fn goto_xy(x: u16, y: u16) {
887 unsafe {
888 dos_like_sys::gotoxy(x as c_int, y as c_int);
889 }
890}
891
892/// Gets the cursor's current X position.
893///
894/// Returns 0 if the video is not in text mode.
895#[inline]
896pub fn where_x() -> u16 {
897 unsafe { dos_like_sys::wherex().max(0) as u16 }
898}
899
900/// Gets the cursor's current Y position.
901///
902/// Returns 0 if the video is not in text mode.
903#[inline]
904pub fn where_y() -> u16 {
905 unsafe { dos_like_sys::wherex().max(0) as u16 }
906}
907
908/// Clears the screen when in text mode.
909pub fn clr_scr() {
910 unsafe {
911 dos_like_sys::clrscr();
912 }
913}
914
915/// Enables the blinking text cursor.
916///
917/// The cursor is visible to the user by default.
918///
919/// Only works in text mode.
920#[inline]
921pub fn curs_on() {
922 unsafe {
923 dos_like_sys::curson();
924 }
925}
926
927/// Hides the text cursor.
928///
929/// Only works in text mode.
930#[inline]
931pub fn curs_off() {
932 unsafe {
933 dos_like_sys::cursoff();
934 }
935}