base_ui/text/
text_renderer.rs

1use super::font::FontRenderer;
2use crate::graphics::Shader;
3use rusttype::PositionedGlyph;
4
5pub struct TextRenderer {
6    font_renderer: FontRenderer,
7    shader: Shader,
8}
9
10impl TextRenderer {
11    pub fn new(font_data: Vec<u8>) -> Self {
12        Self {
13            font_renderer: FontRenderer::new(font_data),
14            shader: Shader::new(
15                include_str!("shaders/text.vert"),
16                include_str!("shaders/text.frag")
17            ),
18        }
19    }
20
21    pub fn render_text(
22        &mut self,
23        text: &str,
24        x: f32,
25        y: f32,
26        scale: f32,
27        screen_width: f32,
28        screen_height: f32,
29        color: [f32; 4]
30    ) {
31        self.shader.use_program();
32        self.shader.set_vec4("textColor", &color);
33
34        let glyphs = self.font_renderer.render_text(text, scale);
35        for glyph in glyphs.iter() {
36            if let Some(bb) = glyph.pixel_bounding_box() {
37                // 글리프의 위치에 x, y 오프셋을 더합니다
38                let adjusted_bb = rusttype::Rect {
39                    min: rusttype::Point {
40                        x: bb.min.x + (x as i32),
41                        y: bb.min.y + (y as i32),
42                    },
43                    max: rusttype::Point {
44                        x: bb.max.x + (x as i32),
45                        y: bb.max.y + (y as i32),
46                    },
47                };
48                self.draw_glyph_with_bb(glyph, &adjusted_bb, screen_width, screen_height);
49            }
50        }
51    }
52
53    /// 주어진 glyph를 래스터화하여 OpenGL 텍스처를 생성하고, 해당 glyph의 사각형(quad)을 그리는 함수.
54    ///
55    /// - `font_renderer`: 폰트 데이터를 관리하는 객체 (래스터화 전에 glyph의 배치 정보가 필요함)
56    /// - `glyph`: 렌더링할 개별 글리프 (rusttype::PositionedGlyph)
57    /// - `screen_width`, `screen_height`: 현재 화면의 픽셀 크기
58    fn draw_glyph_with_bb(
59        &self,
60        glyph: &PositionedGlyph<'_>,
61        bb: &rusttype::Rect<i32>,
62        screen_width: f32,
63        screen_height: f32
64    ) {
65        let width = bb.width() as usize;
66        let height = bb.height() as usize;
67
68        // 픽셀 데이터를 저장할 버퍼 (단일 채널, 8비트)
69        let mut pixel_data = vec![0u8; width * height];
70
71        // glyph.draw() 콜백을 통해 각 픽셀의 커버리지 값을 0.0~1.0 범위의 f32로 받아 0~255로 변환합니다.
72        glyph.draw(|x, y, v| {
73            let idx = (y as usize) * width + (x as usize);
74            pixel_data[idx] = (v * 255.0) as u8;
75        });
76
77        // OpenGL 텍스처 생성 및 업로드
78        let mut tex_id: u32 = 0;
79        unsafe {
80            gl::GenTextures(1, &mut tex_id);
81
82            gl::BindTexture(gl::TEXTURE_2D, tex_id);
83            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
84            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
85            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
86            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
87            // 1바이트 정렬 보장을 위해 설정 (픽셀 데이터가 1바이트 단위이므로)
88            gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
89            gl::TexImage2D(
90                gl::TEXTURE_2D,
91                0,
92                gl::RED as i32,
93                width as i32,
94                height as i32,
95                0,
96                gl::RED,
97                gl::UNSIGNED_BYTE,
98                pixel_data.as_ptr() as *const _
99            );
100        }
101
102        let x0 = bb.min.x as f32;
103        let y0 = bb.min.y as f32;
104        let x1 = bb.max.x as f32;
105        let y1 = bb.max.y as f32;
106
107        // OpenGL의 좌표계는 정규화 장치 좌표(NDC, -1 ~ 1)입니다.
108        let ndc_x0 = (x0 / screen_width) * 2.0 - 1.0;
109        let ndc_y0 = 1.0 - (y0 / screen_height) * 2.0;
110        let ndc_x1 = (x1 / screen_width) * 2.0 - 1.0;
111        let ndc_y1 = 1.0 - (y1 / screen_height) * 2.0;
112
113        // 사각형의 정점 데이터: 각 정점에 3차원 위치와 2차원 텍스처 좌표 (총 5개 요소)
114        let vertices: [f32; 20] = [
115            // 위치(x, y, z)         // 텍스처 좌표 (u, v)
116            ndc_x0,
117            ndc_y1,
118            0.0,
119            0.0,
120            1.0, // 좌측 하단
121            ndc_x1,
122            ndc_y1,
123            0.0,
124            1.0,
125            1.0, // 우측 하단
126            ndc_x1,
127            ndc_y0,
128            0.0,
129            1.0,
130            0.0, // 우측 상단
131            ndc_x0,
132            ndc_y0,
133            0.0,
134            0.0,
135            0.0, // 좌측 상단
136        ];
137        // 사각형을 그리기 위한 인덱스
138        let indices: [u32; 6] = [0, 1, 2, 2, 3, 0];
139
140        // VAO, VBO, EBO를 생성하여 사각형 드로잉 준비
141        let (mut vao, mut vbo, mut ebo) = (0, 0, 0);
142        unsafe {
143            gl::GenVertexArrays(1, &mut vao);
144            gl::GenBuffers(1, &mut vbo);
145            gl::GenBuffers(1, &mut ebo);
146
147            gl::BindVertexArray(vao);
148
149            // VBO 설정
150            gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
151            gl::BufferData(
152                gl::ARRAY_BUFFER,
153                (vertices.len() * std::mem::size_of::<f32>()) as isize,
154                vertices.as_ptr() as *const _,
155                gl::STATIC_DRAW
156            );
157
158            // EBO 설정
159            gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
160            gl::BufferData(
161                gl::ELEMENT_ARRAY_BUFFER,
162                (indices.len() * std::mem::size_of::<u32>()) as isize,
163                indices.as_ptr() as *const _,
164                gl::STATIC_DRAW
165            );
166
167            // 정점 속성: location 0 = position (vec3), location 1 = tex coord (vec2)
168            gl::VertexAttribPointer(
169                0,
170                3,
171                gl::FLOAT,
172                gl::FALSE,
173                (5 * std::mem::size_of::<f32>()) as i32,
174                std::ptr::null()
175            );
176            gl::EnableVertexAttribArray(0);
177            gl::VertexAttribPointer(
178                1,
179                2,
180                gl::FLOAT,
181                gl::FALSE,
182                (5 * std::mem::size_of::<f32>()) as i32,
183                (3 * std::mem::size_of::<f32>()) as *const _
184            );
185            gl::EnableVertexAttribArray(1);
186
187            // 드로우 호출: 텍스처가 활성화된 상태에서 사각형을 그립니다.
188            gl::BindTexture(gl::TEXTURE_2D, tex_id);
189            gl::BindVertexArray(vao);
190            gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, std::ptr::null());
191
192            gl::DeleteBuffers(1, &vbo);
193            gl::DeleteBuffers(1, &ebo);
194            gl::DeleteVertexArrays(1, &vao);
195            gl::DeleteTextures(1, &tex_id);
196        }
197    }
198
199    pub fn font_renderer(&self) -> &FontRenderer {
200        &self.font_renderer
201    }
202}