summaryrefslogblamecommitdiffstats
path: root/wgpu/src/text.rs
blob: 8bcd6d83ab4a8c7638accbceec21813e7cd4f0a6 (plain) (tree)
1
2
3
4
5
6


                          
 
                                               
 






                                                                          

                                                                         
                
                     


                                                                



                                                                 
               
                              


                                    


                                              





                                                                          
 


                                                                      
                                






                                                                               
 







                                                                                           
                                             
                                   
 
                  


                                                        




                                                       
                             

     
                                                               
                                                    



                       
                              





                                           
                         









                                                       






                                  

                                      





                                                               
                                                 


                                






















                                                                 
                                              



                                                                                
















                                                                                
     


                                                                            
                                                                










                                                                              

                                  






                                                         
 
mod font;

use crate::Transformation;

use std::{cell::RefCell, collections::HashMap};

pub const BUILTIN_ICONS: iced_native::Font = iced_native::Font::External {
    name: "iced_wgpu icons",
    bytes: include_bytes!("text/icons.ttf"),
};

pub const CHECKMARK_ICON: char = '\u{F00C}';

const FALLBACK_FONT: &[u8] = include_bytes!("../fonts/Lato-Regular.ttf");

#[derive(Debug)]
pub struct Pipeline {
    draw_brush: RefCell<wgpu_glyph::GlyphBrush<'static, ()>>,
    draw_font_map: RefCell<HashMap<String, wgpu_glyph::FontId>>,

    measure_brush: RefCell<glyph_brush::GlyphBrush<'static, ()>>,
}

impl Pipeline {
    pub fn new(
        device: &wgpu::Device,
        format: wgpu::TextureFormat,
        default_font: Option<&[u8]>,
    ) -> Self {
        // TODO: Font customization
        let font_source = font::Source::new();

        let default_font =
            default_font.map(|slice| slice.to_vec()).unwrap_or_else(|| {
                font_source
                    .load(&[font::Family::SansSerif, font::Family::Serif])
                    .unwrap_or_else(|_| FALLBACK_FONT.to_vec())
            });

        let load_glyph_brush = |font: Vec<u8>| {
            let builder =
                wgpu_glyph::GlyphBrushBuilder::using_fonts_bytes(vec![
                    font.clone()
                ])?;

            Ok((
                builder,
                glyph_brush::GlyphBrushBuilder::using_font_bytes(font).build(),
            ))
        };

        let (brush_builder, measure_brush) = load_glyph_brush(default_font)
            .unwrap_or_else(|_: wgpu_glyph::rusttype::Error| {
                log::warn!("System font failed to load. Falling back to embedded font...");

                load_glyph_brush(FALLBACK_FONT.to_vec()).expect("Load fallback font")
            });

        let draw_brush = brush_builder
            .initial_cache_size((2048, 2048))
            .build(device, format);

        Pipeline {
            draw_brush: RefCell::new(draw_brush),
            draw_font_map: RefCell::new(HashMap::new()),

            measure_brush: RefCell::new(measure_brush),
        }
    }

    pub fn overlay_font(&self) -> wgpu_glyph::FontId {
        wgpu_glyph::FontId(0)
    }

    pub fn queue(&mut self, section: wgpu_glyph::Section<'_>) {
        self.draw_brush.borrow_mut().queue(section);
    }

    pub fn draw_queued(
        &mut self,
        device: &wgpu::Device,
        encoder: &mut wgpu::CommandEncoder,
        target: &wgpu::TextureView,
        transformation: Transformation,
        region: wgpu_glyph::Region,
    ) {
        self.draw_brush
            .borrow_mut()
            .draw_queued_with_transform_and_scissoring(
                device,
                encoder,
                target,
                transformation.into(),
                region,
            )
            .expect("Draw text");
    }

    pub fn measure(
        &self,
        content: &str,
        size: f32,
        font: iced_native::Font,
        bounds: iced_native::Size,
    ) -> (f32, f32) {
        use wgpu_glyph::GlyphCruncher;

        let wgpu_glyph::FontId(font_id) = self.find_font(font);

        let section = wgpu_glyph::Section {
            text: content,
            scale: wgpu_glyph::Scale { x: size, y: size },
            bounds: (bounds.width, bounds.height),
            font_id: wgpu_glyph::FontId(font_id),
            ..Default::default()
        };

        if let Some(bounds) =
            self.measure_brush.borrow_mut().glyph_bounds(section)
        {
            (bounds.width().ceil(), bounds.height().ceil())
        } else {
            (0.0, 0.0)
        }
    }

    pub fn space_width(&self, size: f32) -> f32 {
        use wgpu_glyph::GlyphCruncher;

        let glyph_brush = self.measure_brush.borrow();

        // TODO: Select appropriate font
        let font = &glyph_brush.fonts()[0];

        font.glyph(' ')
            .scaled(wgpu_glyph::Scale { x: size, y: size })
            .h_metrics()
            .advance_width
    }

    pub fn trim_measurement_cache(&mut self) {
        // TODO: We should probably use a `GlyphCalculator` for this. However,
        // it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop.
        // This makes stuff quite inconvenient. A manual method for trimming the
        // cache would make our lives easier.
        loop {
            let action = self
                .measure_brush
                .borrow_mut()
                .process_queued(|_, _| {}, |_| {});

            match action {
                Ok(_) => break,
                Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => {
                    let (width, height) = suggested;

                    self.measure_brush
                        .borrow_mut()
                        .resize_texture(width, height);
                }
            }
        }
    }

    pub fn find_font(&self, font: iced_native::Font) -> wgpu_glyph::FontId {
        match font {
            iced_native::Font::Default => wgpu_glyph::FontId(0),
            iced_native::Font::External { name, bytes } => {
                if let Some(font_id) = self.draw_font_map.borrow().get(name) {
                    return *font_id;
                }

                // TODO: Find a way to share font data
                let _ = self.measure_brush.borrow_mut().add_font_bytes(bytes);

                let font_id =
                    self.draw_brush.borrow_mut().add_font_bytes(bytes);

                let _ = self
                    .draw_font_map
                    .borrow_mut()
                    .insert(String::from(name), font_id);

                font_id
            }
        }
    }
}