1use glyph_brush::GlyphPositioner;
2use glyph_brush::{self, FontId, Layout, Section, Text as GbText};
3pub use glyph_brush::{ab_glyph::PxScale, GlyphBrush, HorizontalAlign as Align};
4use std::borrow::Cow;
5use std::cell::RefCell;
6use std::convert::TryFrom;
7use std::fmt;
8use std::io::Read;
9use std::path;
10use std::rc::Rc;
11
12use super::*;
13
14#[derive(Debug, Copy, Clone, PartialEq, Eq)]
22pub struct Font {
23 font_id: FontId,
24 }
26
27#[derive(Clone, Debug)]
32pub struct FontCache {
33 glyph_brush: Rc<RefCell<GlyphBrush<DrawParam>>>,
34}
35
36impl FontCache {
37 pub fn dimensions(&self, text: &Text) -> Rect {
39 text.calculate_dimensions(&mut self.glyph_brush.borrow_mut())
40 }
41}
42
43#[derive(Clone, Debug)]
49pub struct TextFragment {
50 pub text: String,
52 pub color: Option<Color>,
54 pub font: Option<Font>,
56 pub scale: Option<PxScale>,
58}
59
60impl Default for TextFragment {
61 fn default() -> Self {
62 TextFragment {
63 text: "".into(),
64 color: None,
65 font: None,
66 scale: None,
67 }
68 }
69}
70
71impl TextFragment {
72 pub fn new<T: Into<Self>>(text: T) -> Self {
74 text.into()
75 }
76
77 pub fn color<C: Into<Color>>(mut self, color: C) -> TextFragment {
79 self.color = Some(color.into());
80 self
81 }
82
83 pub fn font(mut self, font: Font) -> TextFragment {
85 self.font = Some(font);
86 self
87 }
88
89 pub fn scale<S: Into<PxScale>>(mut self, scale: S) -> TextFragment {
91 self.scale = Some(scale.into());
92 self
93 }
94}
95
96impl<'a> From<&'a str> for TextFragment {
97 fn from(text: &'a str) -> TextFragment {
98 TextFragment {
99 text: text.to_owned(),
100 ..Default::default()
101 }
102 }
103}
104
105impl From<char> for TextFragment {
106 fn from(ch: char) -> TextFragment {
107 TextFragment {
108 text: ch.to_string(),
109 ..Default::default()
110 }
111 }
112}
113
114impl From<String> for TextFragment {
115 fn from(text: String) -> TextFragment {
116 TextFragment {
117 text,
118 ..Default::default()
119 }
120 }
121}
122
123impl<T> From<(T, Font, f32)> for TextFragment
124where
125 T: Into<TextFragment>,
126{
127 fn from((text, font, scale): (T, Font, f32)) -> TextFragment {
128 text.into().font(font).scale(PxScale::from(scale))
129 }
130}
131
132#[derive(Clone, Debug, Default)]
135struct CachedMetrics {
136 string: Option<String>,
137 width: Option<f32>,
138 height: Option<f32>,
139 glyph_positions: Vec<mint::Point2<f32>>,
140}
141
142#[derive(Debug, Clone)]
149pub struct Text {
150 fragments: Vec<TextFragment>,
151 blend_mode: Option<BlendMode>,
152 filter_mode: FilterMode,
153 bounds: Point2,
154 layout: Layout<glyph_brush::BuiltInLineBreaker>,
155 font_id: FontId,
156 font_scale: PxScale,
157 cached_metrics: RefCell<CachedMetrics>,
158}
159
160impl Default for Text {
161 fn default() -> Self {
162 Text {
163 fragments: Vec::new(),
164 blend_mode: None,
165 filter_mode: FilterMode::Linear,
166 bounds: Point2::new(f32::INFINITY, f32::INFINITY),
167 layout: Layout::default(),
168 font_id: FontId::default(),
169 font_scale: PxScale::from(Font::DEFAULT_FONT_SCALE),
170 cached_metrics: RefCell::new(CachedMetrics::default()),
171 }
172 }
173}
174
175impl Text {
176 pub fn new<F>(fragment: F) -> Text
185 where
186 F: Into<TextFragment>,
187 {
188 let mut text = Text::default();
189 let _ = text.add(fragment);
190 text
191 }
192
193 pub fn add<F>(&mut self, fragment: F) -> &mut Text
195 where
196 F: Into<TextFragment>,
197 {
198 self.fragments.push(fragment.into());
199 self.invalidate_cached_metrics();
200 self
201 }
202
203 pub fn fragments(&self) -> &[TextFragment] {
205 &self.fragments
206 }
207
208 pub fn fragments_mut(&mut self) -> &mut [TextFragment] {
210 self.invalidate_cached_metrics();
211 &mut self.fragments
212 }
213
214 pub fn set_bounds<P>(&mut self, bounds: P, alignment: Align) -> &mut Text
218 where
219 P: Into<mint::Point2<f32>>,
220 {
221 self.bounds = Point2::from(bounds.into());
222 if self.bounds.x == f32::INFINITY {
223 self.layout = Layout::default();
225 } else {
226 self.layout = self.layout.h_align(alignment);
227 }
228 self.invalidate_cached_metrics();
229 self
230 }
231
232 pub fn set_font(&mut self, font: Font, font_scale: PxScale) -> &mut Text {
234 self.font_id = font.font_id;
235 self.font_scale = font_scale;
236 self.invalidate_cached_metrics();
237 self
238 }
239
240 fn generate_varied_section(&self, relative_dest: Point2, color: Option<Color>) -> Section {
242 let sections: Vec<GbText> = self
243 .fragments
244 .iter()
245 .map(|fragment| {
246 let color = fragment.color.or(color).unwrap_or(Color::WHITE);
247 let font_id = fragment
248 .font
249 .map(|font| font.font_id)
250 .unwrap_or(self.font_id);
251 let scale = fragment.scale.unwrap_or(self.font_scale);
252 GbText::default()
253 .with_text(&fragment.text)
254 .with_font_id(font_id)
255 .with_scale(scale)
256 .with_color(<[f32; 4]>::from(color))
257 })
258 .collect();
259
260 let relative_dest_x = {
261 let mut dest_x = relative_dest.x;
263 if self.bounds.x != f32::INFINITY {
264 use glyph_brush::Layout::Wrap;
265 match self.layout {
266 Wrap {
267 h_align: Align::Center,
268 ..
269 } => dest_x += self.bounds.x * 0.5,
270 Wrap {
271 h_align: Align::Right,
272 ..
273 } => dest_x += self.bounds.x,
274 _ => (),
275 }
276 }
277 dest_x
278 };
279 let relative_dest = (relative_dest_x, relative_dest.y);
280 Section {
281 screen_position: relative_dest,
282 bounds: (self.bounds.x, self.bounds.y),
283 layout: self.layout,
284 text: sections,
285 }
286 }
287
288 fn invalidate_cached_metrics(&mut self) {
289 if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
290 *metrics = CachedMetrics::default();
291 return;
294 }
295 warn!("Cached metrics RefCell has been poisoned.");
296 self.cached_metrics = RefCell::new(CachedMetrics::default());
297 }
298
299 pub fn contents(&self) -> String {
301 if let Ok(metrics) = self.cached_metrics.try_borrow() {
302 if let Some(ref string) = metrics.string {
303 return string.clone();
304 }
305 }
306 let string_accm: String = self
307 .fragments
308 .iter()
309 .map(|frag| frag.text.as_str())
310 .collect();
311
312 if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
313 metrics.string = Some(string_accm.clone());
314 }
315 string_accm
316 }
317
318 fn calculate_glyph_positions(
320 &self,
321 gb: &mut GlyphBrush<DrawParam>,
322 ) -> std::cell::Ref<Vec<mint::Point2<f32>>> {
323 if let Ok(metrics) = self.cached_metrics.try_borrow() {
324 if !metrics.glyph_positions.is_empty() {
325 return std::cell::Ref::map(metrics, |metrics| &metrics.glyph_positions);
326 }
327 }
328 let glyph_positions: Vec<mint::Point2<f32>> = {
329 let varied_section = self.generate_varied_section(Point2::new(0.0, 0.0), None);
330 use glyph_brush::GlyphCruncher;
331 gb.glyphs(varied_section)
332 .map(|glyph| glyph.glyph.position)
333 .map(|pos| mint::Point2 { x: pos.x, y: pos.y })
334 .collect()
335 };
336 if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
337 metrics.glyph_positions = glyph_positions;
338 } else {
339 panic!();
340 }
341 if let Ok(metrics) = self.cached_metrics.try_borrow() {
342 std::cell::Ref::map(metrics, |metrics| &metrics.glyph_positions)
343 } else {
344 panic!()
345 }
346 }
347
348 pub fn glyph_positions(&self, context: &Context) -> std::cell::Ref<Vec<mint::Point2<f32>>> {
350 self.calculate_glyph_positions(&mut context.gfx_context.glyph_brush.borrow_mut())
351 }
352
353 fn calculate_dimensions(&self, gb: &mut GlyphBrush<DrawParam>) -> Rect {
355 if let Ok(metrics) = self.cached_metrics.try_borrow() {
356 if let (Some(width), Some(height)) = (metrics.width, metrics.height) {
357 return Rect {
358 x: 0.0,
359 y: 0.0,
360 w: width,
361 h: height,
362 };
363 }
364 }
365 let mut max_width = 0.0;
366 let mut max_height = 0.0;
367 {
368 let varied_section = self.generate_varied_section(Point2::new(0.0, 0.0), None);
369 use glyph_brush::GlyphCruncher;
370 if let Some(bounds) = gb.glyph_bounds(varied_section) {
371 max_width = bounds.width().ceil();
372 max_height = bounds.height().ceil();
373 }
374 }
375 if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
376 metrics.width = Some(max_width);
377 metrics.height = Some(max_height);
378 }
379 Rect {
380 x: 0.0,
381 y: 0.0,
382 w: max_width,
383 h: max_height,
384 }
385 }
386
387 pub fn dimensions(&self, context: &Context) -> Rect {
389 self.calculate_dimensions(&mut context.gfx_context.glyph_brush.borrow_mut())
390 }
391
392 pub fn width(&self, context: &Context) -> f32 {
394 self.dimensions(context).w
395 }
396
397 pub fn height(&self, context: &Context) -> f32 {
399 self.dimensions(context).h
400 }
401}
402
403impl Drawable for Text {
404 fn draw(
405 &self,
406 ctx: &mut Context,
407 quad_ctx: &mut miniquad::graphics::GraphicsContext,
408 param: DrawParam,
409 ) -> GameResult {
410 queue_text(ctx, self, Point2::new(0.0, 0.0), Some(param.color));
412 draw_queued_text(ctx, quad_ctx, param, self.blend_mode, self.filter_mode)
413 }
414
415 fn dimensions(&self, ctx: &mut Context) -> Option<Rect> {
416 Some(self.dimensions(ctx))
417 }
418
419 fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
420 self.blend_mode = mode;
421 }
422
423 fn blend_mode(&self) -> Option<BlendMode> {
424 self.blend_mode
425 }
426}
427
428impl Font {
429 pub const DEFAULT_FONT_SCALE: f32 = 16.0;
431
432 pub fn new<P>(context: &mut Context, path: P) -> GameResult<Font>
434 where
435 P: AsRef<path::Path> + fmt::Debug,
436 {
437 use crate::filesystem;
438 let mut stream = filesystem::open(context, path.as_ref())?;
439 let mut buf = Vec::new();
440 let _ = stream.read_to_end(&mut buf)?;
441
442 Font::new_glyph_font_bytes(context, &buf)
443 }
444
445 pub fn new_glyph_font_bytes(context: &mut Context, bytes: &[u8]) -> GameResult<Self> {
448 let font = glyph_brush::ab_glyph::FontArc::try_from_vec(bytes.to_vec()).unwrap();
451 let font_id = context.gfx_context.glyph_brush.borrow_mut().add_font(font);
452
453 Ok(Font { font_id })
454 }
455
456 pub(crate) fn default_font_bytes() -> &'static [u8] {
458 include_bytes!(concat!(
459 env!("CARGO_MANIFEST_DIR"),
460 "/resources/LiberationMono-Regular.ttf"
461 ))
462 }
463}
464
465impl Default for Font {
466 fn default() -> Self {
467 Font { font_id: FontId(0) }
468 }
469}
470
471pub fn font_cache(context: &Context) -> FontCache {
473 FontCache {
474 glyph_brush: context.gfx_context.glyph_brush.clone(),
475 }
476}
477
478pub fn queue_text<P>(context: &mut Context, batch: &Text, relative_dest: P, color: Option<Color>)
483where
484 P: Into<mint::Point2<f32>>,
485{
486 let p = Point2::from(relative_dest.into());
487 let varied_section = batch.generate_varied_section(p, color);
488 context
489 .gfx_context
490 .glyph_brush
491 .borrow_mut()
492 .queue(varied_section);
493}
494
495pub fn queue_text_raw<'a, S, G>(context: &mut Context, section: S, custom_layout: Option<&G>)
499where
500 S: Into<Cow<'a, Section<'a>>>,
501 G: GlyphPositioner,
502{
503 let brush = &mut context.gfx_context.glyph_brush.borrow_mut();
504 match custom_layout {
505 Some(layout) => brush.queue_custom_layout(section, layout),
506 None => brush.queue(section),
507 }
508}
509
510pub fn draw_queued_text<D>(
521 ctx: &mut Context,
522 quad_ctx: &mut miniquad::graphics::GraphicsContext,
523 param: D,
524 blend: Option<BlendMode>,
525 filter: FilterMode,
526) -> GameResult
527where
528 D: Into<DrawParam>,
529{
530 let param: DrawParam = param.into();
531
532 let gb = &mut ctx.gfx_context.glyph_brush;
533 let gc = &ctx.gfx_context.glyph_cache.texture;
534
535 let action = gb.borrow_mut().process_queued(
536 |rect, tex_data| {
537 let mut tex_data_chunks: Vec<u8> = vec![255; tex_data.len() * 4];
539 for i in 0..tex_data.len() {
540 tex_data_chunks[i * 4 + 3] = tex_data[i];
541 }
542
543 update_texture(quad_ctx, gc, rect, &tex_data_chunks[..])
544 },
545 to_vertex,
546 );
547 match action {
548 Ok(glyph_brush::BrushAction::ReDraw) => {
549 let spritebatch = ctx.gfx_context.glyph_state.clone();
550 let spritebatch = &mut *spritebatch.borrow_mut();
551 spritebatch.set_blend_mode(blend);
552 spritebatch.set_filter(filter);
553 draw(ctx, quad_ctx, &*spritebatch, param)?;
554 }
555 Ok(glyph_brush::BrushAction::Draw(drawparams)) => {
556 let spritebatch = ctx.gfx_context.glyph_state.clone();
558 let spritebatch = &mut *spritebatch.borrow_mut();
559 spritebatch.clear();
560 spritebatch.set_blend_mode(blend);
561 spritebatch.set_filter(filter);
562 for p in &drawparams {
563 let _ = spritebatch.add(*p);
565 }
566 draw(ctx, quad_ctx, &*spritebatch, param)?;
567 }
568 Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => {
569 let (new_width, new_height) = suggested;
570 let data = vec![255; 4 * new_width as usize * new_height as usize];
571 let new_glyph_cache = Image::from_rgba8(
572 ctx,
573 quad_ctx,
574 u16::try_from(new_width).unwrap(),
575 u16::try_from(new_height).unwrap(),
576 &data,
577 )?;
578 ctx.gfx_context.glyph_cache = new_glyph_cache.clone();
579 let spritebatch = ctx.gfx_context.glyph_state.clone();
580 let spritebatch = &mut *spritebatch.borrow_mut();
581 let _ = spritebatch.set_image(new_glyph_cache);
582 ctx.gfx_context
583 .glyph_brush
584 .borrow_mut()
585 .resize_texture(new_width, new_height);
586 }
587 }
588 Ok(())
589}
590
591fn update_texture(
592 ctx: &mut miniquad::Context,
593 texture: &miniquad::Texture,
594 rect: glyph_brush::Rectangle<u32>,
595 tex_data: &[u8],
596) {
597 let offset = [
598 i32::try_from(rect.min[0]).unwrap(),
599 i32::try_from(rect.min[1]).unwrap(),
600 ];
601 let size = [
602 i32::try_from(rect.width()).unwrap(),
603 i32::try_from(rect.height()).unwrap(),
604 ];
605
606 texture.update_texture_part(ctx, offset[0], offset[1], size[0], size[1], tex_data);
607}
608
609fn to_vertex(v: glyph_brush::GlyphVertex) -> DrawParam {
616 let src_rect = Rect {
617 x: v.tex_coords.min.x,
618 y: v.tex_coords.min.y,
619 w: v.tex_coords.max.x - v.tex_coords.min.x,
620 h: v.tex_coords.max.y - v.tex_coords.min.y,
621 };
622 let dest_pt = Point2::new(v.pixel_coords.min.x, v.pixel_coords.min.y);
625 DrawParam::default()
626 .src(src_rect)
627 .dest(dest_pt)
628 .color(v.extra.color.into())
629}