mod custom_glyph;
mod style;
mod styled_text;
mod text;
pub(crate) use custom_glyph::CUSTOM_GLYPH_FAMILY;
pub use custom_glyph::{CUSTOM_GLYPH_CHAR, CustomGlyph, CustomGlyphFont, setup_custom_glyph_font};
pub use style::{Glow, GlyphColoring, Outline, Shadow, TextStyle};
pub use styled_text::{GlyphStyleMap, StyledText, StyledTextBuilder};
pub use text::{DecodedGlyph, Text};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rect {
pub min: glam::Vec2,
pub max: glam::Vec2,
}
impl Rect {
pub fn from_min_max(min: glam::Vec2, max: glam::Vec2) -> Self {
Self { min, max }
}
pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Self {
Self {
min: glam::vec2(x, y),
max: glam::vec2(x + w, y + h),
}
}
pub fn from_min_size(min: glam::Vec2, size: glam::Vec2) -> Self {
Self {
min,
max: min + size,
}
}
pub fn from_max_size(max: glam::Vec2, size: glam::Vec2) -> Self {
Self {
min: max - size,
max,
}
}
pub fn from_center_size(center: glam::Vec2, size: glam::Vec2) -> Self {
Self {
min: center - size / 2.0,
max: center + size / 2.0,
}
}
pub fn from_pivot(pivot_pos: glam::Vec2, pivot_ratio: glam::Vec2, size: glam::Vec2) -> Self {
Self {
min: pivot_pos - size * pivot_ratio,
max: pivot_pos + size * (glam::Vec2::ONE - pivot_ratio),
}
}
pub fn width(&self) -> f32 {
self.max.x - self.min.x
}
pub fn height(&self) -> f32 {
self.max.y - self.min.y
}
pub fn size(&self) -> glam::Vec2 {
self.max - self.min
}
pub fn union(&self, other: Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
pub fn intersection(&self, other: Self) -> Option<Self> {
let rect = Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
};
(rect.min.x <= rect.max.x && rect.min.y <= rect.max.y).then_some(rect)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(transparent)]
pub struct Color(pub glam::Vec4);
impl Color {
pub const WHITE: Self = Self(glam::Vec4::ONE);
pub const BLACK: Self = Self(glam::Vec4::new(0.0, 0.0, 0.0, 1.0));
pub const TRANSPARENT: Self = Self(glam::Vec4::ZERO);
pub fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
Self(glam::vec4(r, g, b, a).clamp(glam::Vec4::ZERO, glam::Vec4::ONE))
}
pub fn from_rgb(r: f32, g: f32, b: f32) -> Self {
Self(glam::vec4(r, g, b, 1.0).clamp(glam::Vec4::ZERO, glam::Vec4::ONE))
}
pub fn from_hsva(h: f32, s: f32, v: f32, a: f32) -> Self {
fn is_between(value: f32, min: f32, max: f32) -> bool {
min <= value && value < max
}
let h = h % 360.0;
let s = s.clamp(0.0, 1.0);
let v = v.clamp(0.0, 1.0);
let a = a.clamp(0.0, 1.0);
let c = v * s;
let h = h / 60.0;
let x = c * (1.0 - ((h % 2.0) - 1.0).abs());
let m = v - c;
let (r, g, b): (f32, f32, f32) = if is_between(h, 0.0, 1.0) {
(c, x, 0.0)
} else if is_between(h, 1.0, 2.0) {
(x, c, 0.0)
} else if is_between(h, 2.0, 3.0) {
(0.0, c, x)
} else if is_between(h, 3.0, 4.0) {
(0.0, x, c)
} else if is_between(h, 4.0, 5.0) {
(x, 0.0, c)
} else {
(c, 0.0, x)
};
Self::from_rgba(r + m, g + m, b + m, a)
}
pub fn from_hsv(h: f32, s: f32, v: f32) -> Self {
Self::from_hsva(h, s, v, 1.0)
}
pub fn from_srgb8(r: u8, g: u8, b: u8) -> Self {
Self::from_rgb(
(r as f32 / 255.0).powf(2.2),
(g as f32 / 255.0).powf(2.2),
(b as f32 / 255.0).powf(2.2),
)
}
pub fn from_srgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
let mut c = Self::from_srgb8(r, g, b);
c.0.w = a as f32 / 255.0;
c
}
pub fn from_hex(rgb: u32) -> Self {
Self::from_srgb8(
((rgb >> 16) & 0xFF) as u8,
((rgb >> 8) & 0xFF) as u8,
(rgb & 0xFF) as u8,
)
}
pub fn with_red(self, r: f32) -> Self {
let r = r.clamp(0.0, 1.0);
Self(glam::vec4(r, self.0.y, self.0.z, self.0.w))
}
pub fn with_green(self, g: f32) -> Self {
let g = g.clamp(0.0, 1.0);
Self(glam::vec4(self.0.x, g, self.0.z, self.0.w))
}
pub fn with_blue(self, b: f32) -> Self {
let b = b.clamp(0.0, 1.0);
Self(glam::vec4(self.0.x, self.0.y, b, self.0.w))
}
pub fn with_alpha(self, a: f32) -> Self {
let a = a.clamp(0.0, 1.0);
Self(glam::vec4(self.0.x, self.0.y, self.0.z, a))
}
pub fn with_red8(self, r: u8) -> Self {
let r = (r as f32 / 255.0).clamp(0.0, 1.0);
Self(glam::vec4(r, self.0.y, self.0.z, self.0.w))
}
pub fn with_green8(self, g: u8) -> Self {
let g = (g as f32 / 255.0).clamp(0.0, 1.0);
Self(glam::vec4(self.0.x, g, self.0.z, self.0.w))
}
pub fn with_blue8(self, b: u8) -> Self {
let b = (b as f32 / 255.0).clamp(0.0, 1.0);
Self(glam::vec4(self.0.x, self.0.y, b, self.0.w))
}
pub fn with_alpha8(self, a: u8) -> Self {
let a = (a as f32 / 255.0).clamp(0.0, 1.0);
Self(glam::vec4(self.0.x, self.0.y, self.0.z, a))
}
}
#[cfg(test)]
mod tests {
use super::*;
use glam::vec2;
fn approx_eq(a: f32, b: f32) -> bool {
(a - b).abs() < 1e-5
}
fn color_approx_eq(a: Color, b: Color) -> bool {
approx_eq(a.0.x, b.0.x)
&& approx_eq(a.0.y, b.0.y)
&& approx_eq(a.0.z, b.0.z)
&& approx_eq(a.0.w, b.0.w)
}
#[test]
fn rect_from_min_max() {
let r = Rect::from_min_max(vec2(1.0, 2.0), vec2(5.0, 6.0));
assert_eq!(r.min, vec2(1.0, 2.0));
assert_eq!(r.max, vec2(5.0, 6.0));
}
#[test]
fn rect_from_xywh() {
let r = Rect::from_xywh(1.0, 2.0, 4.0, 3.0);
assert_eq!(r.min, vec2(1.0, 2.0));
assert_eq!(r.max, vec2(5.0, 5.0));
}
#[test]
fn rect_from_min_size() {
let r = Rect::from_min_size(vec2(1.0, 2.0), vec2(4.0, 3.0));
assert_eq!(r.min, vec2(1.0, 2.0));
assert_eq!(r.max, vec2(5.0, 5.0));
}
#[test]
fn rect_from_max_size() {
let r = Rect::from_max_size(vec2(5.0, 5.0), vec2(4.0, 3.0));
assert_eq!(r.min, vec2(1.0, 2.0));
assert_eq!(r.max, vec2(5.0, 5.0));
}
#[test]
fn rect_from_center_size() {
let r = Rect::from_center_size(vec2(3.0, 3.0), vec2(4.0, 2.0));
assert_eq!(r.min, vec2(1.0, 2.0));
assert_eq!(r.max, vec2(5.0, 4.0));
}
#[test]
fn rect_from_pivot_top_left() {
let r = Rect::from_pivot(vec2(1.0, 2.0), vec2(0.0, 0.0), vec2(4.0, 3.0));
assert_eq!(r.min, vec2(1.0, 2.0));
assert_eq!(r.max, vec2(5.0, 5.0));
}
#[test]
fn rect_from_pivot_center() {
let r = Rect::from_pivot(vec2(3.0, 3.0), vec2(0.5, 0.5), vec2(4.0, 2.0));
assert_eq!(r.min, vec2(1.0, 2.0));
assert_eq!(r.max, vec2(5.0, 4.0));
}
#[test]
fn rect_from_pivot_bottom_right() {
let r = Rect::from_pivot(vec2(5.0, 5.0), vec2(1.0, 1.0), vec2(4.0, 3.0));
assert_eq!(r.min, vec2(1.0, 2.0));
assert_eq!(r.max, vec2(5.0, 5.0));
}
#[test]
fn rect_width_height_size() {
let r = Rect::from_xywh(0.0, 0.0, 8.0, 6.0);
assert_eq!(r.width(), 8.0);
assert_eq!(r.height(), 6.0);
assert_eq!(r.size(), vec2(8.0, 6.0));
}
#[test]
fn rect_union() {
let a = Rect::from_min_max(vec2(0.0, 0.0), vec2(2.0, 2.0));
let b = Rect::from_min_max(vec2(1.0, 1.0), vec2(4.0, 3.0));
let u = a.union(b);
assert_eq!(u.min, vec2(0.0, 0.0));
assert_eq!(u.max, vec2(4.0, 3.0));
}
#[test]
fn rect_union_disjoint() {
let a = Rect::from_min_max(vec2(0.0, 0.0), vec2(1.0, 1.0));
let b = Rect::from_min_max(vec2(3.0, 3.0), vec2(5.0, 5.0));
let u = a.union(b);
assert_eq!(u.min, vec2(0.0, 0.0));
assert_eq!(u.max, vec2(5.0, 5.0));
}
#[test]
fn rect_intersection_overlapping() {
let a = Rect::from_min_max(vec2(0.0, 0.0), vec2(3.0, 3.0));
let b = Rect::from_min_max(vec2(1.0, 1.0), vec2(4.0, 4.0));
let i = a.intersection(b).expect("has intersection");
assert_eq!(i.min, vec2(1.0, 1.0));
assert_eq!(i.max, vec2(3.0, 3.0));
}
#[test]
fn rect_intersection_disjoint_produces_inverted_rect() {
let a = Rect::from_min_max(vec2(0.0, 0.0), vec2(1.0, 1.0));
let b = Rect::from_min_max(vec2(2.0, 2.0), vec2(3.0, 3.0));
let i = a.intersection(b);
assert!(i.is_none());
}
#[test]
fn color_constants() {
assert_eq!(Color::WHITE.0, glam::Vec4::ONE);
assert_eq!(Color::BLACK.0, glam::Vec4::new(0.0, 0.0, 0.0, 1.0));
assert_eq!(Color::TRANSPARENT.0, glam::Vec4::ZERO);
}
#[test]
fn color_from_rgba() {
let c = Color::from_rgba(0.2, 0.4, 0.6, 0.8);
assert!(approx_eq(c.0.x, 0.2));
assert!(approx_eq(c.0.y, 0.4));
assert!(approx_eq(c.0.z, 0.6));
assert!(approx_eq(c.0.w, 0.8));
}
#[test]
fn color_from_rgba_clamped() {
let c = Color::from_rgba(-1.0, 2.0, -0.5, 1.5);
assert_eq!(c.0.x, 0.0);
assert_eq!(c.0.y, 1.0);
assert_eq!(c.0.z, 0.0);
assert_eq!(c.0.w, 1.0);
}
#[test]
fn color_from_rgb_alpha_is_one() {
let c = Color::from_rgb(0.5, 0.5, 0.5);
assert!(approx_eq(c.0.w, 1.0));
}
#[test]
fn color_from_hsva_primary_colors() {
assert!(color_approx_eq(
Color::from_hsva(0.0, 1.0, 1.0, 1.0),
Color::from_rgba(1.0, 0.0, 0.0, 1.0)
));
assert!(color_approx_eq(
Color::from_hsva(120.0, 1.0, 1.0, 1.0),
Color::from_rgba(0.0, 1.0, 0.0, 1.0)
));
assert!(color_approx_eq(
Color::from_hsva(240.0, 1.0, 1.0, 1.0),
Color::from_rgba(0.0, 0.0, 1.0, 1.0)
));
}
#[test]
fn color_from_hsva_zero_saturation_is_grey() {
let c = Color::from_hsva(180.0, 0.0, 0.5, 1.0);
assert!(approx_eq(c.0.x, 0.5));
assert!(approx_eq(c.0.y, 0.5));
assert!(approx_eq(c.0.z, 0.5));
}
#[test]
fn color_from_hsva_hue_wraps() {
let c360 = Color::from_hsva(360.0, 1.0, 1.0, 1.0);
let c0 = Color::from_hsva(0.0, 1.0, 1.0, 1.0);
assert!(color_approx_eq(c360, c0));
}
#[test]
fn color_from_hsva_alpha_clamped() {
let c = Color::from_hsva(0.0, 0.0, 0.0, 2.0);
assert!(approx_eq(c.0.w, 1.0));
}
#[test]
fn color_from_hsv_alpha_is_one() {
let c = Color::from_hsv(0.0, 1.0, 1.0);
assert!(approx_eq(c.0.w, 1.0));
}
#[test]
fn color_from_srgb8_black_white() {
let black = Color::from_srgb8(0, 0, 0);
assert!(color_approx_eq(black, Color::BLACK));
let white = Color::from_srgb8(255, 255, 255);
assert!(color_approx_eq(white, Color::WHITE));
}
#[test]
fn color_from_srgb8_primary_red() {
let c = Color::from_srgb8(255, 0, 0);
assert!(approx_eq(c.0.x, 1.0));
assert!(approx_eq(c.0.y, 0.0));
assert!(approx_eq(c.0.z, 0.0));
assert!(approx_eq(c.0.w, 1.0));
}
#[test]
fn color_from_srgba8_alpha_channel() {
let c = Color::from_srgba8(255, 255, 255, 128);
assert!(approx_eq(c.0.w, 128.0 / 255.0));
assert!(approx_eq(c.0.x, 1.0));
}
#[test]
fn color_from_hex() {
let red = Color::from_hex(0xFF0000);
assert!(color_approx_eq(red, Color::from_srgb8(255, 0, 0)));
let green = Color::from_hex(0x00FF00);
assert!(color_approx_eq(green, Color::from_srgb8(0, 255, 0)));
let blue = Color::from_hex(0x0000FF);
assert!(color_approx_eq(blue, Color::from_srgb8(0, 0, 255)));
let white = Color::from_hex(0xFFFFFF);
assert!(color_approx_eq(white, Color::WHITE));
}
#[test]
fn color_with_channel_setters() {
let base = Color::from_rgba(0.1, 0.2, 0.3, 0.4);
assert!(approx_eq(base.with_red(0.9).0.x, 0.9));
assert!(approx_eq(base.with_green(0.9).0.y, 0.9));
assert!(approx_eq(base.with_blue(0.9).0.z, 0.9));
assert!(approx_eq(base.with_alpha(0.9).0.w, 0.9));
let after_red = base.with_red(0.9);
assert!(approx_eq(after_red.0.y, 0.2));
assert!(approx_eq(after_red.0.z, 0.3));
assert!(approx_eq(after_red.0.w, 0.4));
}
#[test]
fn color_with_channel_setters_clamped() {
let base = Color::WHITE;
assert_eq!(base.with_red(-1.0).0.x, 0.0);
assert_eq!(base.with_green(2.0).0.y, 1.0);
assert_eq!(base.with_blue(-0.1).0.z, 0.0);
assert_eq!(base.with_alpha(1.5).0.w, 1.0);
}
#[test]
fn color_with_channel8_setters() {
let base = Color::BLACK;
assert!(approx_eq(base.with_red8(255).0.x, 1.0));
assert!(approx_eq(base.with_green8(255).0.y, 1.0));
assert!(approx_eq(base.with_blue8(255).0.z, 1.0));
assert!(approx_eq(base.with_alpha8(255).0.w, 1.0));
assert!(approx_eq(base.with_red8(0).0.x, 0.0));
assert!(approx_eq(base.with_alpha8(128).0.w, 128.0 / 255.0));
}
}