1use batbox_color::*;
2use batbox_la::*;
3use batbox_num::*;
4use geng_camera::*;
5use serde::{Deserialize, Serialize};
6use std::cell::RefCell;
7use std::collections::{HashMap, HashSet};
8use std::rc::Rc;
9use ugli::Ugli;
10
11#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
12pub enum DistanceMode {
13 Euclid,
14 Max,
15}
16
17#[derive(Clone)]
18pub struct Options {
19 pub pixel_size: f32,
20 pub max_distance: f32,
21 pub antialias: bool,
22 pub distance_mode: DistanceMode,
23 }
25
26impl Default for Options {
27 fn default() -> Self {
28 Self {
29 pixel_size: 64.0,
30 max_distance: 0.25,
31 antialias: true,
32 distance_mode: DistanceMode::Euclid,
33 }
34 }
35}
36
37#[derive(Debug, Clone, ugli::Vertex)]
38pub struct GlyphInstance {
39 pub i_pos: vec2<f32>,
40 pub i_size: vec2<f32>,
41 pub i_uv_pos: vec2<f32>,
42 pub i_uv_size: vec2<f32>,
43}
44
45#[derive(Debug)]
46struct GlyphMetrics {
47 uv: Aabb2<f32>,
48 pos: Aabb2<f32>,
49}
50
51#[derive(Debug)]
52struct Glyph {
53 metrics: Option<GlyphMetrics>,
54 advance_x: f32,
55}
56
57pub struct Font {
58 ugli: Ugli,
59 sdf_program: Rc<ugli::Program>,
60 program: Rc<ugli::Program>,
61 glyphs: HashMap<char, Glyph>,
62 atlas: ugli::Texture,
63 max_distance: f32,
64 ascender: f32,
65 descender: f32,
66 line_gap: f32,
67}
68
69impl Font {
70 pub fn default(ugli: &Ugli) -> Self {
71 Self::new(ugli, include_bytes!("default.ttf"), &Options::default()).unwrap()
72 }
73 pub fn new(ugli: &Ugli, data: &[u8], options: &Options) -> anyhow::Result<Self> {
74 let shader_lib = geng_shader::Library::new(ugli, options.antialias, None);
75 let face = ttf_parser::Face::parse(data, 0)?;
76 struct RawGlyph {
77 id: ttf_parser::GlyphId,
78 code_point: char,
79 bounding_box: Option<Aabb2<f32>>,
80 }
81 let unit_scale = 1.0 / (face.ascender() - face.descender()) as f32;
82 let scale = options.pixel_size * unit_scale;
83 let mut raw_glyphs = Vec::new();
84 let mut found = HashSet::new();
85 for subtable in face.tables().cmap.unwrap().subtables {
86 if !subtable.is_unicode() {
87 continue;
88 }
89 subtable.codepoints(|code_point| {
90 let id = match subtable.glyph_index(code_point) {
91 Some(id) => id,
92 None => return,
93 };
94 let code_point = match char::from_u32(code_point) {
95 Some(code_point) => code_point,
96 None => return,
97 };
98 if found.contains(&code_point) {
99 return;
100 }
101 found.insert(code_point);
102 let bounding_box = face.glyph_bounding_box(id).map(|rect| {
103 Aabb2 {
104 min: vec2(rect.x_min, rect.y_min),
105 max: vec2(rect.x_max, rect.y_max),
106 }
107 .map(|x| x as f32 * scale)
108 });
109 raw_glyphs.push(RawGlyph {
110 id,
111 code_point,
112 bounding_box,
113 })
114 });
115 }
116 raw_glyphs.sort_unstable_by_key(|glyph| {
117 glyph
118 .bounding_box
119 .map_or(0, |bb| -bb.height().ceil() as i32)
120 });
121 let mut glyphs: HashMap<char, Glyph> = HashMap::with_capacity(raw_glyphs.len());
122 let mut width = 0;
123 let mut x = 0;
124 let mut y = 0;
125 let mut row_height = 0;
126 let renderable_glyphs: Vec<&RawGlyph> = raw_glyphs
127 .iter()
128 .filter(|g| g.bounding_box.is_some())
129 .collect();
130 for glyph in &raw_glyphs {
131 if glyph.bounding_box.is_none() {
132 glyphs.insert(
133 glyph.code_point,
134 Glyph {
135 metrics: None,
136 advance_x: face.glyph_hor_advance(glyph.id).unwrap_or(0) as f32
137 * unit_scale,
138 },
139 );
140 }
141 }
142 for (i, glyph) in renderable_glyphs.iter().enumerate() {
143 let glyph_pos = glyph
144 .bounding_box
145 .unwrap()
146 .extend_uniform(options.max_distance * options.pixel_size);
147 let glyph_size = glyph_pos.size().map(|x| x.ceil() as usize);
148 if (y == 0 && i * i >= renderable_glyphs.len())
149 || (y > 0 && x > 0 && x + glyph_size.x > width)
150 {
151 x = 0;
152 y += row_height;
153 row_height = 0;
154 }
155 let uv = Aabb2::point(vec2(x, y)).extend_positive(glyph_size);
156 x = uv.max.x;
157 row_height = row_height.max(uv.height());
158 width = width.max(x);
159 glyphs.insert(
160 glyph.code_point,
161 Glyph {
162 metrics: Some(GlyphMetrics {
163 uv: uv.map(|x| x as f32),
164 pos: glyph_pos.map(|x| x / options.pixel_size),
165 }),
166 advance_x: face.glyph_hor_advance(glyph.id).unwrap_or(0) as f32 * unit_scale,
167 },
168 );
169 }
170 let height = y + row_height;
171 let atlas_size = vec2(width, height);
172 for glyph in glyphs.values_mut() {
173 if let Some(metrics) = &mut glyph.metrics {
174 metrics.uv = metrics.uv.map_bounds(|b| b / atlas_size.map(|x| x as f32));
175 }
176 }
177 let mut atlas = ugli::Texture::new_uninitialized(ugli, atlas_size);
178 {
179 let mut depth_buffer = ugli::Renderbuffer::new(ugli, atlas_size);
180 let mut framebuffer = ugli::Framebuffer::new(
181 ugli,
182 ugli::ColorAttachment::Texture(&mut atlas),
183 ugli::DepthAttachment::RenderbufferWithStencil(&mut depth_buffer),
184 );
185 let framebuffer = &mut framebuffer;
186 ugli::clear(
187 framebuffer,
188 Some(Rgba::TRANSPARENT_BLACK),
189 Some(1.0),
190 Some(0),
191 );
192
193 #[derive(ugli::Vertex, Copy, Clone)]
194 struct Vertex {
195 a_pos: vec2<f32>,
196 a_dist_pos: vec2<f32>,
197 }
198 struct Builder {
199 distance_mesh: Vec<Vertex>,
200 stencil_mesh: Vec<Vertex>,
201 pos: vec2<f32>,
202 contour_start: vec2<f32>,
203 scale: f32,
204 offset: vec2<f32>,
205 options: Options,
206 }
207 impl Builder {
208 fn new_glyph_at(&mut self, offset: vec2<f32>) {
209 self.offset = offset;
210 }
211 fn add_triangle_fan(&mut self, mid: Vertex, vs: impl IntoIterator<Item = Vertex>) {
212 use itertools::Itertools;
213 for (a, b) in vs.into_iter().tuple_windows() {
214 self.distance_mesh.push(mid);
215 self.distance_mesh.push(a);
216 self.distance_mesh.push(b);
217 }
218 }
219 fn add_triangle_fan2(&mut self, vs: impl IntoIterator<Item = Vertex>) {
220 let mut vs = vs.into_iter();
221 let first = vs.next().unwrap();
222 self.add_triangle_fan(first, vs);
223 }
224 fn add_line(&mut self, a: vec2<f32>, b: vec2<f32>) {
225 let radius = self.options.max_distance * self.options.pixel_size;
226 self.stencil_mesh.push(Vertex {
227 a_pos: self.offset,
228 a_dist_pos: vec2::ZERO,
229 });
230 self.stencil_mesh.push(Vertex {
231 a_pos: a,
232 a_dist_pos: vec2::ZERO,
233 });
234 self.stencil_mesh.push(Vertex {
235 a_pos: b,
236 a_dist_pos: vec2::ZERO,
237 });
238 let unit_quad = Aabb2::point(vec2::ZERO).extend_uniform(1.0);
239 let a_quad = Aabb2::point(a).extend_uniform(radius);
240 let b_quad = Aabb2::point(b).extend_uniform(radius);
241 self.add_triangle_fan2(
242 itertools::izip![a_quad.corners(), unit_quad.corners()]
243 .map(|(a_pos, a_dist_pos)| Vertex { a_pos, a_dist_pos }),
244 );
245 self.add_triangle_fan2(
246 itertools::izip![b_quad.corners(), unit_quad.corners()]
247 .map(|(a_pos, a_dist_pos)| Vertex { a_pos, a_dist_pos }),
248 );
249 let n = (b - a).rotate_90().normalize_or_zero() * radius;
250 self.add_triangle_fan2([
251 Vertex {
252 a_pos: a + n,
253 a_dist_pos: vec2(0.0, 1.0),
254 },
255 Vertex {
256 a_pos: b + n,
257 a_dist_pos: vec2(0.0, 1.0),
258 },
259 Vertex {
260 a_pos: b - n,
261 a_dist_pos: vec2(0.0, -1.0),
262 },
263 Vertex {
264 a_pos: a - n,
265 a_dist_pos: vec2(0.0, -1.0),
266 },
267 ]);
268 }
269 }
270 fn quad_bezier(p0: vec2<f32>, p1: vec2<f32>, p2: vec2<f32>, t: f32) -> vec2<f32> {
271 (1.0 - t).sqr() * p0 + 2.0 * (1.0 - t) * t * p1 + t.sqr() * p2
272 }
273 fn cubic_bezier(
274 p0: vec2<f32>,
275 p1: vec2<f32>,
276 p2: vec2<f32>,
277 p3: vec2<f32>,
278 t: f32,
279 ) -> vec2<f32> {
280 (1.0 - t) * quad_bezier(p0, p1, p2, t) + t * quad_bezier(p1, p2, p3, t)
281 }
282 const N: usize = 10;
283 impl ttf_parser::OutlineBuilder for Builder {
284 fn move_to(&mut self, x: f32, y: f32) {
285 self.contour_start = vec2(x, y);
286 self.pos = vec2(x, y) * self.scale + self.offset;
287 }
288 fn line_to(&mut self, x: f32, y: f32) {
289 let a = self.pos;
290 self.pos = vec2(x, y) * self.scale + self.offset;
291 let b = self.pos;
292 self.add_line(a, b);
293 }
294 fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
295 let p0 = self.pos;
297 let p1 = vec2(x1, y1) * self.scale + self.offset;
298 let p2 = vec2(x, y) * self.scale + self.offset;
299 for i in 1..=N {
300 let t = i as f32 / N as f32;
301 let p = quad_bezier(p0, p1, p2, t);
302 self.add_line(self.pos, p);
303 self.pos = p;
304 }
305 }
306 fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
307 let p0 = self.pos;
309 let p1 = vec2(x1, y1) * self.scale + self.offset;
310 let p2 = vec2(x2, y2) * self.scale + self.offset;
311 let p3 = vec2(x, y) * self.scale + self.offset;
312 for i in 1..=N {
313 let t = i as f32 / N as f32;
314 let p = cubic_bezier(p0, p1, p2, p3, t);
315 self.add_line(self.pos, p);
316 self.pos = p;
317 }
318 }
319 fn close(&mut self) {
320 self.line_to(self.contour_start.x, self.contour_start.y);
321 }
322 }
323 let mut builder = Builder {
324 distance_mesh: vec![],
325 stencil_mesh: vec![],
326 pos: vec2::ZERO,
327 contour_start: vec2::ZERO,
328 scale,
329 offset: vec2::ZERO,
330 options: options.clone(),
331 };
332 for glyph in &raw_glyphs {
333 if glyph.bounding_box.is_none() {
334 continue;
335 }
336 builder.new_glyph_at(
337 (glyphs[&glyph.code_point]
338 .metrics
339 .as_ref()
340 .unwrap()
341 .uv
342 .bottom_left()
343 * atlas_size.map(|x| x as f32))
344 .map(|x| x + options.max_distance * options.pixel_size)
345 - glyph.bounding_box.unwrap().bottom_left(),
346 );
347 face.outline_glyph(glyph.id, &mut builder);
348 }
349 let line_shader = shader_lib
350 .compile(match options.distance_mode {
351 DistanceMode::Euclid => include_str!("ttf_line_euclid.glsl"),
352 DistanceMode::Max => include_str!("ttf_line_max.glsl"),
353 })
354 .unwrap();
355 let white_shader = shader_lib.compile(include_str!("white.glsl")).unwrap();
356 ugli::draw(
357 framebuffer,
358 &line_shader,
359 ugli::DrawMode::Triangles,
360 &ugli::VertexBuffer::new_static(ugli, builder.stencil_mesh),
361 ugli::uniforms! {
362 u_framebuffer_size: framebuffer.size(),
363 },
364 ugli::DrawParameters {
365 stencil_mode: Some(ugli::StencilMode {
366 back_face: ugli::FaceStencilMode {
367 test: ugli::StencilTest {
368 condition: ugli::Condition::Always,
369 reference: 0,
370 mask: 0,
371 },
372 op: ugli::StencilOp::always(ugli::StencilOpFunc::IncrementWrap),
373 },
374 front_face: ugli::FaceStencilMode {
375 test: ugli::StencilTest {
376 condition: ugli::Condition::Always,
377 reference: 0,
378 mask: 0,
379 },
380 op: ugli::StencilOp::always(ugli::StencilOpFunc::DecrementWrap),
381 },
382 }),
383 write_color: false,
384 ..Default::default()
385 },
386 );
387 ugli::draw(
388 framebuffer,
389 &line_shader,
390 ugli::DrawMode::Triangles,
391 &ugli::VertexBuffer::new_static(ugli, builder.distance_mesh),
392 ugli::uniforms! {
393 u_framebuffer_size: framebuffer.size(),
394 },
395 ugli::DrawParameters {
396 blend_mode: Some(ugli::BlendMode::combined(ugli::ChannelBlendMode {
397 src_factor: ugli::BlendFactor::One,
398 dst_factor: ugli::BlendFactor::One,
399 equation: ugli::BlendEquation::Max,
400 })),
401 ..Default::default()
403 },
404 );
405 ugli::draw(
406 framebuffer,
407 &white_shader,
408 ugli::DrawMode::TriangleFan,
409 &ugli::VertexBuffer::new_static(
410 ugli,
411 Aabb2::point(vec2(0, 0))
412 .extend_positive(framebuffer.size())
413 .corners()
414 .into_iter()
415 .map(|p| Vertex {
416 a_pos: p.map(|x| x as f32),
417 a_dist_pos: vec2::ZERO,
418 })
419 .collect(),
420 ),
421 ugli::uniforms! {
422 u_framebuffer_size: framebuffer.size(),
423 },
424 ugli::DrawParameters {
425 stencil_mode: Some(ugli::StencilMode::always(ugli::FaceStencilMode {
426 test: ugli::StencilTest {
427 condition: ugli::Condition::NotEqual,
428 reference: 0,
429 mask: 0xff,
430 },
431 op: ugli::StencilOp::always(ugli::StencilOpFunc::Keep),
432 })),
433 blend_mode: Some(ugli::BlendMode::combined(ugli::ChannelBlendMode {
434 src_factor: ugli::BlendFactor::OneMinusDstColor,
435 dst_factor: ugli::BlendFactor::Zero,
436 equation: ugli::BlendEquation::Add,
437 })),
438 ..Default::default()
439 },
440 );
441 }
442 thread_local! { pub static SHADERS: RefCell<Option<[Rc<ugli::Program>; 2]>> = Default::default(); };
443 let [program, sdf_program] = SHADERS.with(|shaders| {
444 fn map<T, R>(a: &[T; 2], f: impl Fn(&T) -> R) -> [R; 2] {
445 let [a, b] = a;
446 [f(a), f(b)]
447 }
448 map(
449 shaders.borrow_mut().get_or_insert_with(|| {
450 [
451 shader_lib.compile(include_str!("shader.glsl")).unwrap(),
452 shader_lib.compile(include_str!("sdf.glsl")).unwrap(),
453 ]
454 .map(Rc::new)
455 }),
456 |shader| Rc::clone(shader),
457 )
458 });
459 Ok(Self {
460 ugli: ugli.clone(),
461 program,
462 sdf_program,
463 glyphs,
464 atlas,
465 max_distance: options.max_distance,
466 ascender: face.ascender() as f32 * unit_scale,
467 descender: face.descender() as f32 * unit_scale,
468 line_gap: face.line_gap() as f32 * unit_scale,
469 })
470 }
471
472 pub fn max_distance(&self) -> f32 {
473 self.max_distance
474 }
475
476 pub fn ascender(&self) -> f32 {
477 self.ascender
478 }
479
480 pub fn descender(&self) -> f32 {
481 self.descender
482 }
483
484 pub fn line_gap(&self) -> f32 {
485 self.line_gap
486 }
487
488 pub fn measure(&self, text: &str, align: vec2<TextAlign>) -> Option<Aabb2<f32>> {
489 self.draw_with(text, align, |glyphs, _| {
490 if glyphs.is_empty() {
491 return None;
492 }
493 Some(
494 Aabb2::points_bounding_box(
495 glyphs
496 .iter()
497 .flat_map(|glyph| [glyph.i_pos, glyph.i_pos + glyph.i_size]),
498 )
499 .unwrap()
500 .extend_uniform(-self.max_distance),
501 )
502 })
503 }
504
505 pub fn advance(&self, text: &str) -> f32 {
506 let mut x = 0.0;
507 for glyph in text.chars().filter_map(move |c| self.glyphs.get(&c)) {
508 x += glyph.advance_x;
510 }
511 x
512 }
513
514 pub fn draw_with<R>(
515 &self,
516 text: &str,
517 align: vec2<TextAlign>,
518 f: impl FnOnce(&[GlyphInstance], &ugli::Texture) -> R,
519 ) -> R {
520 let mut vs = Vec::<GlyphInstance>::new();
521 let mut pos = vec2::ZERO;
522 let mut size_x: f32 = 0.0;
523 let mut line_width: f32 = 0.0;
524 let mut line_start = 0;
525 for c in text.chars() {
526 if c == '\n' {
527 for v in &mut vs[line_start..] {
528 v.i_pos.x -= line_width * align.x.0;
529 }
530 pos.x = 0.0;
531 pos.y -= 1.0;
532 line_width = 0.0;
533 line_start = vs.len();
534 continue;
535 }
536 let Some(glyph) = self.glyphs.get(&c) else {
537 continue;
538 };
539 if let Some(metrics) = &glyph.metrics {
541 let instance = GlyphInstance {
542 i_pos: pos + metrics.pos.bottom_left(),
543 i_size: metrics.pos.size(),
544 i_uv_pos: metrics.uv.bottom_left(),
545 i_uv_size: metrics.uv.size(),
546 };
547 line_width =
548 line_width.max(instance.i_pos.x + instance.i_size.x - self.max_distance);
549 size_x = size_x.max(line_width);
550 vs.push(instance);
551 }
552 pos.x += glyph.advance_x;
553 }
554 for v in &mut vs[line_start..] {
555 v.i_pos.x -= line_width * align.x.0;
556 }
557 for v in &mut vs {
558 v.i_pos.y += -pos.y * (1.0 - align.y.0) - align.y.0;
559 }
560 f(&vs, &self.atlas)
561 }
562
563 pub fn draw(
564 &self,
565 framebuffer: &mut ugli::Framebuffer,
566 camera: &(impl AbstractCamera2d + ?Sized),
567 text: &str,
568 align: vec2<TextAlign>,
569 transform: mat3<f32>,
570 color: Rgba<f32>,
571 ) {
572 self.draw_with_outline(
573 framebuffer,
574 camera,
575 text,
576 align,
577 transform,
578 color,
579 0.0,
580 Rgba { a: 0.0, ..color },
581 );
582 }
583
584 #[allow(clippy::too_many_arguments)]
585 pub fn draw_with_outline(
586 &self,
587 framebuffer: &mut ugli::Framebuffer,
588 camera: &(impl AbstractCamera2d + ?Sized),
589 text: &str,
590 align: vec2<TextAlign>,
591 transform: mat3<f32>,
592 color: Rgba<f32>,
593 outline_size: f32,
594 outline_color: Rgba<f32>,
595 ) {
596 self.draw_with(text, align, |glyphs, texture| {
597 let framebuffer_size = framebuffer.size();
598 ugli::draw(
599 framebuffer,
600 &self.program,
601 ugli::DrawMode::TriangleFan,
602 ugli::instanced(
604 &ugli::VertexBuffer::new_dynamic(
605 &self.ugli,
606 Aabb2::point(vec2::ZERO)
607 .extend_positive(vec2(1.0, 1.0))
608 .corners()
609 .into_iter()
610 .map(|v| Vertex { a_pos: v, a_vt: v })
611 .collect(),
612 ),
613 &ugli::VertexBuffer::new_dynamic(&self.ugli, glyphs.to_vec()),
614 ),
615 (
616 ugli::uniforms! {
617 u_texture: texture,
618 u_model_matrix: transform,
619 u_color: color,
620 u_outline_dist: outline_size / self.max_distance,
621 u_outline_color: outline_color,
622 },
623 camera.uniforms(framebuffer_size.map(|x| x as f32)),
624 ),
625 ugli::DrawParameters {
626 depth_func: None,
627 blend_mode: Some(ugli::BlendMode::straight_alpha()),
628 ..Default::default()
629 },
630 );
631 });
632 }
633
634 pub fn create_text_sdf(
635 &self,
636 text: &str,
637 line_align: TextAlign,
638 pixel_size: f32,
639 ) -> Option<ugli::Texture> {
640 let align = vec2(line_align, TextAlign::BOTTOM);
641 let aabb = self.measure(text, align)?;
642 let texture_size = (vec2(
643 aabb.width() + 2.0 * self.max_distance(),
644 text.chars().filter(|c| *c == '\n').count() as f32 + 1.0 + 2.0 * self.max_distance(),
645 ) * pixel_size)
646 .map(|x| x.ceil() as usize);
647 let mut texture = ugli::Texture::new_uninitialized(&self.ugli, texture_size);
648 let framebuffer = &mut ugli::Framebuffer::new_color(
649 &self.ugli,
650 ugli::ColorAttachment::Texture(&mut texture),
651 );
652 ugli::clear(framebuffer, Some(Rgba::TRANSPARENT_BLACK), None, None);
653 self.draw_with(text, align, |glyphs, atlas| {
654 ugli::draw(
655 framebuffer,
656 &self.sdf_program,
657 ugli::DrawMode::TriangleFan,
658 ugli::instanced(
659 &ugli::VertexBuffer::new_dynamic(
660 &self.ugli,
661 Aabb2::point(vec2::ZERO)
662 .extend_positive(vec2(1.0, 1.0))
663 .corners()
664 .into_iter()
665 .map(|v| Vertex { a_pos: v, a_vt: v })
666 .collect(),
667 ),
668 &ugli::VertexBuffer::new_dynamic(&self.ugli, glyphs.to_vec()),
669 ),
670 ugli::uniforms! {
671 u_texture: atlas,
672 u_matrix: mat3::ortho(aabb.extend_uniform(self.max_distance())),
673 },
674 ugli::DrawParameters {
675 blend_mode: Some(ugli::BlendMode::combined(ugli::ChannelBlendMode {
676 src_factor: ugli::BlendFactor::One,
677 dst_factor: ugli::BlendFactor::One,
678 equation: ugli::BlendEquation::Max,
679 })),
680 ..Default::default()
681 },
682 );
683 });
684 Some(texture)
685 }
686}
687
688#[derive(ugli::Vertex, Debug)]
689pub struct Vertex {
690 pub a_pos: vec2<f32>,
691 pub a_vt: vec2<f32>,
692}
693
694#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
695pub struct TextAlign(pub f32);
696
697impl TextAlign {
698 pub const LEFT: Self = Self(0.0);
699 pub const BOTTOM: Self = Self(0.0);
700 pub const TOP: Self = Self(1.0);
701 pub const CENTER: Self = Self(0.5);
702 pub const RIGHT: Self = Self(1.0);
703}