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}