Skip to content

Commit 4204ecf

Browse files
committed
fix incomplete function for mutli fonts and add example
1 parent 0a41183 commit 4204ecf

3 files changed

Lines changed: 320 additions & 3 deletions

File tree

examples/fonts.rs

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
#[path = "ctx.rs"]
2+
mod ctx;
3+
4+
use ctx::Ctx;
5+
use glyph_brush::{FontId, OwnedSection};
6+
use glyph_brush::ab_glyph::FontRef;
7+
use std::sync::Arc;
8+
use std::time::{Duration, Instant};
9+
use wgpu_text::glyph_brush::{
10+
BuiltInLineBreaker, Layout, OwnedText, Section, Text, VerticalAlign,
11+
};
12+
use wgpu_text::{BrushBuilder, TextBrush};
13+
use winit::application::ApplicationHandler;
14+
use winit::event::{ElementState, WindowEvent};
15+
use winit::event::{KeyEvent, MouseScrollDelta};
16+
use winit::event_loop::{self, ActiveEventLoop, ControlFlow};
17+
use winit::keyboard::{Key, NamedKey};
18+
use winit::window::Window;
19+
20+
struct State<'a> {
21+
// Use an `Option` to allow the window to not be available until the
22+
// application is properly running.
23+
window: Option<Arc<Window>>,
24+
font1: &'a [u8],
25+
font2: &'a [u8],
26+
brush: Option<TextBrush<FontRef<'a>>>,
27+
font_size: f32,
28+
section_0: Option<OwnedSection>,
29+
section_1: Option<OwnedSection>,
30+
31+
target_framerate: Duration,
32+
delta_time: Instant,
33+
fps_update_time: Instant,
34+
fps: i32,
35+
36+
// wgpu
37+
ctx: Option<Ctx>,
38+
}
39+
40+
impl ApplicationHandler for State<'_> {
41+
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
42+
let window = Arc::new(
43+
event_loop
44+
.create_window(
45+
Window::default_attributes()
46+
.with_title("wgpu-text: 'simple' example"),
47+
)
48+
.unwrap(),
49+
);
50+
51+
self.ctx = Some(Ctx::new(window.clone()));
52+
53+
let ctx = self.ctx.as_ref().unwrap();
54+
let device = &ctx.device;
55+
let config = &ctx.config;
56+
57+
self.brush = Some(BrushBuilder::using_font_bytes_vec(vec![self.font1, self.font2]).unwrap().build(
58+
device,
59+
config.width,
60+
config.height,
61+
config.format,
62+
));
63+
64+
self.section_0 = Some(
65+
Section::default()
66+
.add_text(
67+
Text::new(
68+
"Try typing some text,\n \
69+
del - delete all, backspace - remove last character",
70+
)
71+
.with_scale(self.font_size)
72+
.with_color([0.9, 0.5, 0.5, 1.0])
73+
.with_font_id(FontId(0)),
74+
)
75+
.with_bounds((config.width as f32 * 0.4, config.height as f32))
76+
.with_layout(
77+
Layout::default()
78+
.v_align(VerticalAlign::Center)
79+
.line_breaker(BuiltInLineBreaker::AnyCharLineBreaker),
80+
)
81+
.with_screen_position((50.0, config.height as f32 * 0.5))
82+
.to_owned(),
83+
);
84+
85+
self.section_1 = Some(
86+
Section::default()
87+
.add_text(
88+
Text::new("Other section")
89+
.with_scale(40.0)
90+
.with_color([0.2, 0.5, 0.8, 1.0])
91+
.with_font_id(FontId(1)),
92+
)
93+
.with_bounds((config.width as f32 * 0.5, config.height as f32))
94+
.with_layout(
95+
Layout::default()
96+
.v_align(VerticalAlign::Top)
97+
.line_breaker(BuiltInLineBreaker::AnyCharLineBreaker),
98+
)
99+
.with_screen_position((500.0, config.height as f32 * 0.2))
100+
.to_owned(),
101+
);
102+
103+
self.window = Some(window);
104+
}
105+
106+
fn window_event(
107+
&mut self,
108+
elwt: &ActiveEventLoop,
109+
_window_id: winit::window::WindowId,
110+
event: WindowEvent,
111+
) {
112+
match event {
113+
WindowEvent::Resized(new_size) => {
114+
let ctx = self.ctx.as_mut().unwrap();
115+
let queue = &ctx.queue;
116+
let device = &ctx.device;
117+
let config = &mut ctx.config;
118+
let surface = &ctx.surface;
119+
let section_0 = self.section_0.as_mut().unwrap();
120+
let brush = self.brush.as_mut().unwrap();
121+
122+
config.width = new_size.width.max(1);
123+
config.height = new_size.height.max(1);
124+
surface.configure(device, config);
125+
126+
section_0.bounds = (config.width as f32 * 0.4, config.height as _);
127+
section_0.screen_position.1 = config.height as f32 * 0.5;
128+
129+
brush.resize_view(config.width as f32, config.height as f32, queue);
130+
131+
// You can also do this!
132+
// brush.update_matrix(wgpu_text::ortho(config.width, config.height), &queue);
133+
}
134+
WindowEvent::CloseRequested => elwt.exit(),
135+
WindowEvent::KeyboardInput {
136+
event:
137+
KeyEvent {
138+
logical_key,
139+
state: ElementState::Pressed,
140+
..
141+
},
142+
..
143+
} => match logical_key {
144+
Key::Named(k) => match k {
145+
NamedKey::Escape => elwt.exit(),
146+
NamedKey::Delete => self.section_0.as_mut().unwrap().text.clear(),
147+
NamedKey::Backspace
148+
if !self.section_0.clone().unwrap().text.is_empty() =>
149+
{
150+
let section = self.section_0.as_mut().unwrap();
151+
let mut end_text = section.text.remove(section.text.len() - 1);
152+
end_text.text.pop();
153+
if !end_text.text.is_empty() {
154+
self.section_0.as_mut().unwrap().text.push(end_text.clone());
155+
}
156+
}
157+
_ => (),
158+
},
159+
Key::Character(char) => {
160+
let c = char.as_str();
161+
if c != "\u{7f}" && c != "\u{8}" {
162+
if self.section_0.clone().unwrap().text.is_empty() {
163+
self.section_0.as_mut().unwrap().text.push(
164+
OwnedText::default()
165+
.with_scale(self.font_size)
166+
.with_color([0.9, 0.5, 0.5, 1.0]),
167+
);
168+
}
169+
self.section_0.as_mut().unwrap().text.push(
170+
OwnedText::new(c.to_string())
171+
.with_scale(self.font_size)
172+
.with_color([0.9, 0.5, 0.5, 1.0]),
173+
);
174+
}
175+
}
176+
_ => (),
177+
},
178+
WindowEvent::MouseWheel {
179+
delta: MouseScrollDelta::LineDelta(_, y),
180+
..
181+
} => {
182+
// increase/decrease font size
183+
let mut size = self.font_size;
184+
if y > 0.0 {
185+
size += (size / 4.0).max(2.0)
186+
} else {
187+
size *= 4.0 / 5.0
188+
};
189+
self.font_size = (size.clamp(3.0, 25000.0) * 2.0).round() / 2.0;
190+
}
191+
WindowEvent::RedrawRequested => {
192+
let brush = self.brush.as_mut().unwrap();
193+
let ctx = self.ctx.as_ref().unwrap();
194+
let queue = &ctx.queue;
195+
let device = &ctx.device;
196+
let config = &ctx.config;
197+
let surface = &ctx.surface;
198+
let section_0 = self.section_0.as_ref().unwrap();
199+
let section_1 = self.section_1.as_ref().unwrap();
200+
201+
match brush.queue(device, queue, [section_0, section_1]) {
202+
Ok(_) => (),
203+
Err(err) => {
204+
panic!("{err}");
205+
}
206+
};
207+
208+
let frame = match surface.get_current_texture() {
209+
Ok(frame) => frame,
210+
Err(_) => {
211+
surface.configure(device, config);
212+
surface
213+
.get_current_texture()
214+
.expect("Failed to acquire next surface texture!")
215+
}
216+
};
217+
let view = frame
218+
.texture
219+
.create_view(&wgpu::TextureViewDescriptor::default());
220+
221+
let mut encoder =
222+
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
223+
label: Some("Command Encoder"),
224+
});
225+
226+
{
227+
let mut rpass =
228+
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
229+
label: Some("Render Pass"),
230+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
231+
view: &view,
232+
depth_slice: None,
233+
resolve_target: None,
234+
ops: wgpu::Operations {
235+
load: wgpu::LoadOp::Clear(wgpu::Color {
236+
r: 0.2,
237+
g: 0.2,
238+
b: 0.3,
239+
a: 1.,
240+
}),
241+
store: wgpu::StoreOp::Store,
242+
},
243+
})],
244+
depth_stencil_attachment: None,
245+
timestamp_writes: None,
246+
occlusion_query_set: None,
247+
multiview_mask: None,
248+
});
249+
250+
brush.draw(&mut rpass);
251+
}
252+
253+
queue.submit([encoder.finish()]);
254+
frame.present();
255+
}
256+
_ => (),
257+
}
258+
}
259+
260+
fn new_events(&mut self, _elwt: &ActiveEventLoop, _cause: winit::event::StartCause) {
261+
if self.target_framerate <= self.delta_time.elapsed()
262+
&& let Some(window) = self.window.clone().as_mut()
263+
{
264+
window.request_redraw();
265+
self.delta_time = Instant::now();
266+
self.fps += 1;
267+
if self.fps_update_time.elapsed().as_millis() > 1000 {
268+
window.set_title(&format!(
269+
"wgpu-text: 'simple' example, FPS: {}",
270+
self.fps
271+
));
272+
self.fps = 0;
273+
self.fps_update_time = Instant::now();
274+
}
275+
}
276+
}
277+
278+
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
279+
println!("Exiting!");
280+
}
281+
}
282+
283+
// TODO text layout of characters like 'š, ć, ž, đ' doesn't work correctly.
284+
fn main() {
285+
if std::env::var("RUST_LOG").is_err() {
286+
unsafe {
287+
std::env::set_var("RUST_LOG", "error");
288+
}
289+
}
290+
env_logger::init();
291+
292+
let event_loop = event_loop::EventLoop::new().unwrap();
293+
event_loop.set_control_flow(ControlFlow::Poll);
294+
295+
let mut state = State {
296+
window: None,
297+
font1: include_bytes!("fonts/DejaVuSans.ttf"),
298+
font2: include_bytes!("fonts/ClimateCrisis.ttf"),
299+
brush: None,
300+
font_size: 25.,
301+
section_0: None,
302+
section_1: None,
303+
304+
// FPS and window updating:
305+
// change '60.0' if you want different FPS cap
306+
target_framerate: Duration::from_secs_f64(1.0 / 60.0),
307+
delta_time: Instant::now(),
308+
fps_update_time: Instant::now(),
309+
fps: 0,
310+
311+
ctx: None,
312+
};
313+
314+
let _ = event_loop.run_app(&mut state);
315+
}

examples/fonts/ClimateCrisis.ttf

76.4 KB
Binary file not shown.

src/brush.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,12 @@ impl BrushBuilder<()> {
200200

201201
/// Creates a [`BrushBuilder`] with multiple fonts byte data.
202202
pub fn using_font_bytes_vec(
203-
data: &[u8],
203+
data_vec: Vec<&[u8]>,
204204
) -> Result<BrushBuilder<FontRef<'_>>, InvalidFont> {
205-
let font = FontRef::try_from_slice(data)?;
206-
Ok(BrushBuilder::using_fonts(vec![font]))
205+
let fonts = data_vec.iter().map(|data|
206+
FontRef::try_from_slice(data)
207+
).collect::<Result<Vec<FontRef>, InvalidFont>>()?;
208+
Ok(BrushBuilder::using_fonts(fonts))
207209
}
208210

209211
/// Creates a [`BrushBuilder`] with multiple [`Font`].

0 commit comments

Comments
 (0)