1use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct BookFont {
11 pub font_id: String,
13 pub size_px: f32,
15}
16
17#[derive(Debug, Default)]
20pub struct BookFontRegistry {
21 pub fonts: HashMap<String, Vec<u8>>,
22}
23
24impl BookFontRegistry {
25 pub fn register(&mut self, id: impl Into<String>, ttf: Vec<u8>) {
26 self.fonts.insert(id.into(), ttf);
27 }
28 pub fn get(&self, id: &str) -> Option<&[u8]> {
29 self.fonts.get(id).map(Vec::as_slice)
30 }
31}
32
33#[derive(Debug, Clone)]
35pub struct GlyphInfo {
36 pub u0: f32,
37 pub v0: f32,
38 pub u1: f32,
39 pub v1: f32,
40 pub width: u32,
41 pub height: u32,
42 pub xoff: f32,
43 pub yoff: f32,
44 pub advance: f32,
45}
46
47pub struct FontAtlas {
50 pub pixels: Vec<u8>,
51 pub atlas_size: u32,
52 pub glyphs: HashMap<char, GlyphInfo>,
53 pub line_height: f32,
54}
55
56#[cfg(feature = "fonts")]
58const ATLAS_CHARS: &str = " !\"#$%&'()*+,-./0123456789:;<=>?@\
59 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`\
60 abcdefghijklmnopqrstuvwxyz{|}~\
61 ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞß\
62 àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ";
63
64impl FontAtlas {
65 pub fn build(font_data: &[u8], size_px: f32) -> Option<FontAtlas> {
68 Self::build_impl(font_data, size_px)
69 }
70
71 #[cfg(feature = "fonts")]
72 fn build_impl(font_data: &[u8], size_px: f32) -> Option<FontAtlas> {
73 let font = ::fontdue::Font::from_bytes(
75 font_data,
76 ::fontdue::FontSettings {
77 scale: size_px,
78 ..Default::default()
79 },
80 )
81 .ok()?;
82
83 const ATLAS_W: u32 = 512;
84 const ATLAS_H: u32 = 512;
85 let mut pixels = vec![0u8; (ATLAS_W * ATLAS_H * 4) as usize];
86 let mut glyphs = HashMap::new();
87
88 let row_h = size_px.ceil() as u32 + 2;
89 let mut cx: u32 = 1;
90 let mut cy: u32 = 1;
91
92 for ch in ATLAS_CHARS.chars() {
93 let (metrics, bitmap) = font.rasterize(ch, size_px);
94 let gw = metrics.width as u32;
95 let gh = metrics.height as u32;
96
97 if gw == 0 || gh == 0 {
98 glyphs.insert(
99 ch,
100 GlyphInfo {
101 u0: 0.0,
102 v0: 0.0,
103 u1: 0.0,
104 v1: 0.0,
105 width: 0,
106 height: 0,
107 xoff: metrics.xmin as f32,
108 yoff: metrics.ymin as f32,
109 advance: metrics.advance_width,
110 },
111 );
112 continue;
113 }
114 if cx + gw + 1 >= ATLAS_W {
115 cx = 1;
116 cy += row_h;
117 }
118 if cy + gh + 1 >= ATLAS_H {
119 break;
120 }
121
122 for row in 0..gh {
123 for col in 0..gw {
124 let alpha = bitmap[(row * gw + col) as usize];
125 let idx = ((cy + row) * ATLAS_W + (cx + col)) as usize * 4;
126 pixels[idx] = 255;
127 pixels[idx + 1] = 255;
128 pixels[idx + 2] = 255;
129 pixels[idx + 3] = alpha;
130 }
131 }
132
133 glyphs.insert(
134 ch,
135 GlyphInfo {
136 u0: cx as f32 / ATLAS_W as f32,
137 v0: cy as f32 / ATLAS_H as f32,
138 u1: (cx + gw) as f32 / ATLAS_W as f32,
139 v1: (cy + gh) as f32 / ATLAS_H as f32,
140 width: gw,
141 height: gh,
142 xoff: metrics.xmin as f32,
143 yoff: metrics.ymin as f32,
144 advance: metrics.advance_width,
145 },
146 );
147 cx += gw + 1;
148 }
149
150 Some(FontAtlas {
151 pixels,
152 atlas_size: ATLAS_W,
153 glyphs,
154 line_height: size_px * 1.2,
155 })
156 }
157
158 #[cfg(not(feature = "fonts"))]
159 fn build_impl(_font_data: &[u8], _size_px: f32) -> Option<FontAtlas> {
160 None
161 }
162
163 pub fn measure_width(&self, text: &str) -> f32 {
165 text.chars()
166 .map(|c| self.glyphs.get(&c).map(|g| g.advance).unwrap_or(0.0))
167 .sum()
168 }
169}