1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! Extensions for [`crate::renderer::pen::Pen`].

use super::{RenderExt, RenderMode};
use crate::{
    color::Rgba,
    geo::{Point, Rect, Size},
    renderer::{pen::Pen, Paster},
    texture::Texture,
    ttf::font::Font,
};

/// X-axis alignment of the text.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum TextAlignX {
    /// Aligns to the left. Moves the text to the right so that the pivot comes the left edge.
    Left,
    /// Aligns to the center.
    Center,
    /// Aligns to the right. Moves the text to the left so that the pivot comes the right edge.
    Right,
}

impl Default for TextAlignX {
    fn default() -> Self {
        Self::Left
    }
}

/// Y-axis alignment of the text.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum TextAlignY {
    /// Aligns to the top. Moves the text to the down so that the pivot comes the top edge.
    Top,
    /// Aligns to the center.
    Center,
    /// Aligns to the bottom. Moves the text to the up so that the pivot comes the bottom edge.
    Bottom,
}

impl Default for TextAlignY {
    fn default() -> Self {
        Self::Top
    }
}

/// Alignments of the text.
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)]
pub struct TextAlign {
    /// The X-axis alignment of the text.
    pub x: TextAlignX,
    /// The Y-axis alignment of the text.
    pub y: TextAlignY,
}

/// Options to render the text for [`FontRenderExt::text`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FontRenderOptions {
    align: TextAlign,
    pivot: Point,
    mode: RenderMode,
}

impl FontRenderOptions {
    /// Constructs a default option.
    pub fn new() -> Self {
        FontRenderOptions {
            align: TextAlign::default(),
            pivot: Point::default(),
            mode: RenderMode::Blended {
                foreground: Rgba {
                    r: 0,
                    g: 0,
                    b: 0,
                    a: 255,
                },
            },
        }
    }

    /// Sets the render mode with color.
    pub fn mode(mut self, mode: RenderMode) -> Self {
        self.mode = mode;
        self
    }

    /// Sets the alignment.
    pub fn align(mut self, align: TextAlign) -> Self {
        self.align = align;
        self
    }

    /// Sets the pivot point.
    pub fn pivot(mut self, pivot: Point) -> Self {
        self.pivot = pivot;
        self
    }

    fn aligned_pos(&self, Size { width, height }: Size) -> Point {
        let x = match self.align.x {
            TextAlignX::Left => self.pivot.x,
            TextAlignX::Center => self.pivot.x - width as i32 / 2,
            TextAlignX::Right => self.pivot.x - width as i32,
        };
        let y = match self.align.y {
            TextAlignY::Top => self.pivot.y,
            TextAlignY::Center => self.pivot.y - height as i32 / 2,
            TextAlignY::Bottom => self.pivot.y - height as i32,
        };
        Point { x, y }
    }
}

impl Default for FontRenderOptions {
    fn default() -> Self {
        Self::new()
    }
}

/// An extension for [`Pen`] to render a text.
pub trait FontRenderExt {
    /// Renders a text to the area with the font.
    fn text(&self, font: &Font, text: &str, options: FontRenderOptions);
}

impl FontRenderExt for Pen<'_> {
    fn text(&self, font: &Font, text: &str, options: FontRenderOptions) {
        if text.is_empty() {
            return;
        }
        let surface = font
            .render(text, options.mode)
            .expect("rendering text failed");
        let texture = Texture::from_surface(self.renderer(), &surface);
        let size = font
            .rendered_size(text)
            .expect("calculating text size failed");
        let up_left = options.aligned_pos(size);
        Paster::new(self.renderer()).paste(&texture, Some(Rect { up_left, size }));
    }
}