make fonts generic over GeneralBitmap types

this also includes a semi-important change where the existing
BitmaskCharacter draw implementation will now not draw anything if
FontRenderOpts::None is passed in, instead of just substituting color 0.

this probably makes more sense this way anyway
This commit is contained in:
Gered 2023-03-09 16:19:52 -05:00
parent 434bd6dbed
commit 986a2a9677
12 changed files with 62 additions and 30 deletions

View file

@ -3,6 +3,7 @@ use std::path::Path;
use anyhow::Result; use anyhow::Result;
use ggdt::audio::*; use ggdt::audio::*;
use ggdt::graphics::*;
use ggdt::graphics::indexed::*; use ggdt::graphics::indexed::*;
use ggdt::system::*; use ggdt::system::*;
use ggdt::utils::rnd_value; use ggdt::utils::rnd_value;

View file

@ -3,6 +3,7 @@ use std::path::Path;
use anyhow::Result; use anyhow::Result;
use ggdt::*; use ggdt::*;
use ggdt::graphics::*;
use ggdt::graphics::indexed::*; use ggdt::graphics::indexed::*;
use ggdt::math::*; use ggdt::math::*;
use ggdt::system::*; use ggdt::system::*;

View file

@ -1,5 +1,6 @@
use ggdt::entities::*; use ggdt::entities::*;
use ggdt::events::*; use ggdt::events::*;
use ggdt::graphics::*;
use ggdt::graphics::indexed::*; use ggdt::graphics::indexed::*;
use ggdt::math::*; use ggdt::math::*;
use ggdt::states::*; use ggdt::states::*;

View file

@ -2,6 +2,7 @@ use std::path::Path;
use ggdt::base::*; use ggdt::base::*;
use ggdt::entities::*; use ggdt::entities::*;
use ggdt::graphics::font::*;
use ggdt::graphics::indexed::*; use ggdt::graphics::indexed::*;
use ggdt::states::*; use ggdt::states::*;
use ggdt::system::*; use ggdt::system::*;

View file

@ -1,6 +1,7 @@
use anyhow::Result; use anyhow::Result;
use ggdt::{SCREEN_BOTTOM, SCREEN_RIGHT}; use ggdt::{SCREEN_BOTTOM, SCREEN_RIGHT};
use ggdt::graphics::*;
use ggdt::graphics::indexed::*; use ggdt::graphics::indexed::*;
use ggdt::system::*; use ggdt::system::*;
use ggdt::utils::rnd_value; use ggdt::utils::rnd_value;

View file

@ -35,6 +35,9 @@ pub trait GeneralBitmap: Sized + Clone {
/// Returns the height of the bitmap in pixels. /// Returns the height of the bitmap in pixels.
fn height(&self) -> u32; fn height(&self) -> u32;
/// Returns the current clipping region set on this bitmap.
fn clip_region(&self) -> &Rect;
/// Returns a rect representing the full bitmap boundaries, ignoring the current clipping /// Returns a rect representing the full bitmap boundaries, ignoring the current clipping
/// region set on this bitmap. /// region set on this bitmap.
fn full_bounds(&self) -> Rect; fn full_bounds(&self) -> Rect;
@ -111,6 +114,10 @@ impl GeneralBitmap for indexed::Bitmap {
self.height() self.height()
} }
fn clip_region(&self) -> &Rect {
self.clip_region()
}
#[inline] #[inline]
fn full_bounds(&self) -> Rect { fn full_bounds(&self) -> Rect {
self.full_bounds() self.full_bounds()

View file

@ -4,12 +4,14 @@ use std::io::{BufReader, BufWriter, Cursor};
use std::path::Path; use std::path::Path;
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use num_traits::{PrimInt, Unsigned};
use thiserror::Error; use thiserror::Error;
use crate::graphics::*;
use crate::graphics::indexed::*; use crate::graphics::indexed::*;
use crate::math::*; use crate::math::*;
pub static VGA_FONT_BYTES: &[u8] = include_bytes!("../../../assets/vga.fnt"); pub static VGA_FONT_BYTES: &[u8] = include_bytes!("../../assets/vga.fnt");
pub const NUM_CHARS: usize = 256; pub const NUM_CHARS: usize = 256;
pub const CHAR_HEIGHT: usize = 8; pub const CHAR_HEIGHT: usize = 8;
@ -25,14 +27,16 @@ pub enum FontError {
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum FontRenderOpts { pub enum FontRenderOpts<PixelType: PrimInt + Unsigned> {
Color(u8), Color(PixelType),
None, None,
} }
pub trait Character { pub trait Character {
fn bounds(&self) -> &Rect; fn bounds(&self) -> &Rect;
fn draw(&self, dest: &mut Bitmap, x: i32, y: i32, opts: FontRenderOpts); fn draw<BitmapType>(&self, dest: &mut BitmapType, x: i32, y: i32, opts: FontRenderOpts<BitmapType::PixelType>)
where
BitmapType: GeneralBitmap;
} }
pub trait Font { pub trait Font {
@ -41,7 +45,9 @@ pub trait Font {
fn character(&self, ch: char) -> &Self::CharacterType; fn character(&self, ch: char) -> &Self::CharacterType;
fn space_width(&self) -> u8; fn space_width(&self) -> u8;
fn line_height(&self) -> u8; fn line_height(&self) -> u8;
fn measure(&self, text: &str, opts: FontRenderOpts) -> (u32, u32); fn measure<PixelType>(&self, text: &str, opts: FontRenderOpts<PixelType>) -> (u32, u32)
where
PixelType: PrimInt + Unsigned;
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -56,7 +62,10 @@ impl Character for BitmaskCharacter {
&self.bounds &self.bounds
} }
fn draw(&self, dest: &mut Bitmap, x: i32, y: i32, opts: FontRenderOpts) { fn draw<BitmapType>(&self, dest: &mut BitmapType, x: i32, y: i32, opts: FontRenderOpts<BitmapType::PixelType>)
where
BitmapType: GeneralBitmap
{
// out of bounds check // out of bounds check
if ((x + self.bounds.width as i32) < dest.clip_region().x) if ((x + self.bounds.width as i32) < dest.clip_region().x)
|| ((y + self.bounds.height as i32) < dest.clip_region().y) || ((y + self.bounds.height as i32) < dest.clip_region().y)
@ -68,7 +77,11 @@ impl Character for BitmaskCharacter {
let color = match opts { let color = match opts {
FontRenderOpts::Color(color) => color, FontRenderOpts::Color(color) => color,
_ => 0, // this kind of highlights a weakness of this design i guess. what does it mean to render a BitmaskFont,
// which has no inherent colour information in it, when there is no specific render colour passed in?
// TODO: is it better to return an error here? should a BitmaskFont have a "default colour" to fall back to?
// or, should a Bitmap have a "default colour" property we could fall back to? not sure!
_ => return,
}; };
// TODO: i'm sure this can be optimized, lol // TODO: i'm sure this can be optimized, lol
@ -195,7 +208,10 @@ impl Font for BitmaskFont {
self.line_height self.line_height
} }
fn measure(&self, text: &str, _opts: FontRenderOpts) -> (u32, u32) { fn measure<PixelType>(&self, text: &str, _opts: FontRenderOpts<PixelType>) -> (u32, u32)
where
PixelType: PrimInt + Unsigned
{
if text.is_empty() { if text.is_empty() {
return (0, 0); return (0, 0);
} }
@ -249,29 +265,29 @@ pub mod tests {
{ {
let font = BitmaskFont::load_from_file(Path::new("./assets/vga.fnt"))?; let font = BitmaskFont::load_from_file(Path::new("./assets/vga.fnt"))?;
assert_eq!((40, 8), font.measure("Hello", FontRenderOpts::None)); assert_eq!((40, 8), font.measure("Hello", FontRenderOpts::<u8>::None));
assert_eq!((40, 16), font.measure("Hello\nthere", FontRenderOpts::None)); assert_eq!((40, 16), font.measure("Hello\nthere", FontRenderOpts::<u8>::None));
assert_eq!((88, 24), font.measure("longer line\nshort\nthe end", FontRenderOpts::None)); assert_eq!((88, 24), font.measure("longer line\nshort\nthe end", FontRenderOpts::<u8>::None));
assert_eq!((0, 0), font.measure("", FontRenderOpts::None)); assert_eq!((0, 0), font.measure("", FontRenderOpts::<u8>::None));
assert_eq!((0, 0), font.measure(" ", FontRenderOpts::None)); assert_eq!((0, 0), font.measure(" ", FontRenderOpts::<u8>::None));
assert_eq!((40, 16), font.measure("\nhello", FontRenderOpts::None)); assert_eq!((40, 16), font.measure("\nhello", FontRenderOpts::<u8>::None));
assert_eq!((0, 0), font.measure("\n", FontRenderOpts::None)); assert_eq!((0, 0), font.measure("\n", FontRenderOpts::<u8>::None));
assert_eq!((40, 8), font.measure("hello\n", FontRenderOpts::None)); assert_eq!((40, 8), font.measure("hello\n", FontRenderOpts::<u8>::None));
assert_eq!((40, 24), font.measure("hello\n\nthere", FontRenderOpts::None)); assert_eq!((40, 24), font.measure("hello\n\nthere", FontRenderOpts::<u8>::None));
} }
{ {
let font = BitmaskFont::load_from_file(Path::new("./test-assets/small.fnt"))?; let font = BitmaskFont::load_from_file(Path::new("./test-assets/small.fnt"))?;
assert_eq!((22, 7), font.measure("Hello", FontRenderOpts::None)); assert_eq!((22, 7), font.measure("Hello", FontRenderOpts::<u8>::None));
assert_eq!((24, 14), font.measure("Hello\nthere", FontRenderOpts::None)); assert_eq!((24, 14), font.measure("Hello\nthere", FontRenderOpts::<u8>::None));
assert_eq!((50, 21), font.measure("longer line\nshort\nthe end", FontRenderOpts::None)); assert_eq!((50, 21), font.measure("longer line\nshort\nthe end", FontRenderOpts::<u8>::None));
assert_eq!((0, 0), font.measure("", FontRenderOpts::None)); assert_eq!((0, 0), font.measure("", FontRenderOpts::<u8>::None));
assert_eq!((0, 0), font.measure(" ", FontRenderOpts::None)); assert_eq!((0, 0), font.measure(" ", FontRenderOpts::<u8>::None));
assert_eq!((21, 14), font.measure("\nhello", FontRenderOpts::None)); assert_eq!((21, 14), font.measure("\nhello", FontRenderOpts::<u8>::None));
assert_eq!((0, 0), font.measure("\n", FontRenderOpts::None)); assert_eq!((0, 0), font.measure("\n", FontRenderOpts::<u8>::None));
assert_eq!((21, 7), font.measure("hello\n", FontRenderOpts::None)); assert_eq!((21, 7), font.measure("hello\n", FontRenderOpts::<u8>::None));
assert_eq!((24, 21), font.measure("hello\n\nthere", FontRenderOpts::None)); assert_eq!((24, 21), font.measure("hello\n\nthere", FontRenderOpts::<u8>::None));
} }
Ok(()) Ok(())

View file

@ -1,5 +1,6 @@
use std::mem::swap; use std::mem::swap;
use crate::graphics::*;
use crate::graphics::indexed::*; use crate::graphics::indexed::*;
use crate::math::*; use crate::math::*;
@ -76,13 +77,13 @@ impl Bitmap {
/// Renders a single character using the font given. /// Renders a single character using the font given.
#[inline] #[inline]
pub fn print_char<T: Font>(&mut self, ch: char, x: i32, y: i32, opts: FontRenderOpts, font: &T) { pub fn print_char<T: Font>(&mut self, ch: char, x: i32, y: i32, opts: FontRenderOpts<u8>, font: &T) {
font.character(ch) font.character(ch)
.draw(self, x, y, opts); .draw(self, x, y, opts);
} }
/// Renders the string of text using the font given. /// Renders the string of text using the font given.
pub fn print_string<T: Font>(&mut self, text: &str, x: i32, y: i32, opts: FontRenderOpts, font: &T) { pub fn print_string<T: Font>(&mut self, text: &str, x: i32, y: i32, opts: FontRenderOpts<u8>, font: &T) {
let mut current_x = x; let mut current_x = x;
let mut current_y = y; let mut current_y = y;
for ch in text.chars() { for ch in text.chars() {

View file

@ -3,10 +3,8 @@
pub use self::bitmap::*; pub use self::bitmap::*;
pub use self::blendmap::*; pub use self::blendmap::*;
pub use self::font::*;
pub use self::palette::*; pub use self::palette::*;
pub mod bitmap; pub mod bitmap;
pub mod blendmap; pub mod blendmap;
pub mod font;
pub mod palette; pub mod palette;

View file

@ -1,7 +1,9 @@
pub use self::bitmap::*; pub use self::bitmap::*;
pub use self::bitmapatlas::*; pub use self::bitmapatlas::*;
pub use self::font::*;
pub mod bitmap; pub mod bitmap;
pub mod bitmapatlas; pub mod bitmapatlas;
pub mod font;
pub mod indexed; pub mod indexed;
pub mod rgb; pub mod rgb;

View file

@ -2,6 +2,7 @@
//! instance to provide something resembling an old DOS VGA mode 13h style experience (there are differences, however). //! instance to provide something resembling an old DOS VGA mode 13h style experience (there are differences, however).
//! //!
//! ```no_run //! ```no_run
//! use ggdt::graphics::*;
//! use ggdt::graphics::indexed::*; //! use ggdt::graphics::indexed::*;
//! use ggdt::system::*; //! use ggdt::system::*;
//! //!
@ -30,6 +31,7 @@
use sdl2::video::Window; use sdl2::video::Window;
use crate::system::*; use crate::system::*;
use crate::graphics::*;
use crate::graphics::indexed::*; use crate::graphics::indexed::*;
/// Configuration / builder for configuring and constructing an instance of [`DosLike`]. /// Configuration / builder for configuring and constructing an instance of [`DosLike`].

View file

@ -2,6 +2,7 @@ use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
use ggdt::{SCREEN_HEIGHT, SCREEN_WIDTH}; use ggdt::{SCREEN_HEIGHT, SCREEN_WIDTH};
use ggdt::graphics::*;
use ggdt::graphics::indexed::*; use ggdt::graphics::indexed::*;
fn setup() -> (Bitmap, Palette) { fn setup() -> (Bitmap, Palette) {