mcsdf 0.1.0

Multi-channel signed distance fields rasterizer library
Documentation
use super::geometry::{Curve, Line, Rect};
use super::shape::{AllocatedShape, Segment, Shape};
use super::texture::{Texture, TextureViewAllocator};
use cgmath::Point2;
use rusttype::{Contour, Scale, Segment as FontSegment};
use rusttype::{Error as RustTypeError, Font as RustTypeFont};
use std::collections::HashMap;
use std::iter::{once, FromIterator};
use std::mem::replace;
use std::sync::{Arc, Mutex};

#[derive(Debug)]
pub enum FontError {
    CannotLoadFont,
}

impl From<RustTypeError> for FontError {
    fn from(_error: RustTypeError) -> Self {
        FontError::CannotLoadFont
    }
}

struct GlyphInfo {
    texture_id: u32,
    texture_view: Rect<f32>,
}

pub struct GlyphLayout {
    pub texture_id: u32,
    pub screen_coord: Rect<f32>,
    pub texture_coord: Rect<f32>,
}

pub struct TextBlockLayout {
    pub font_size: u8,
    pub shadow_size: u8,
    pub bounding_box: Rect<f32>,
    pub glyph_layouts: Vec<GlyphLayout>,
}

pub struct TextureRenderBatch {
    pub texture_id: u32,
    pub texture: Arc<Mutex<Texture>>,
    pub allocated_shapes: Vec<AllocatedShape>,
}

struct TextureMetadata {
    texture: Arc<Mutex<Texture>>,
    allocator: TextureViewAllocator,
    allocated_shapes: Vec<AllocatedShape>,
}

pub struct Font {
    texture_metadatas: Vec<TextureMetadata>,
    free_texture_index: u32,
    texture_width: u32,
    texture_height: u32,
    font_size: u8,
    shadow_size: u8,
    font: RustTypeFont<'static>,
    glyphs: HashMap<char, Option<GlyphInfo>>,
}

impl Font {
    pub fn new(
        texture_width: u32,
        texture_height: u32,
        font_size: u8,
        shadow_size: u8,
        font_data: Vec<u8>,
    ) -> Result<Self, FontError> {
        let font = RustTypeFont::from_bytes(font_data)?;
        let (texture, allocator) = Texture::new(texture_width, texture_height);
        let texture_metadatas = vec![TextureMetadata {
            texture: Arc::new(Mutex::new(texture)),
            allocator,
            allocated_shapes: Vec::new(),
        }];

        Ok(Font {
            texture_metadatas,
            free_texture_index: 0,
            texture_width,
            texture_height,
            font_size,
            shadow_size,
            font,
            glyphs: HashMap::new(),
        })
    }

    pub fn invalidate(&mut self) {
        let (texture, allocator) = Texture::new(self.texture_width, self.texture_height);
        let texture_metadatas = vec![TextureMetadata {
            texture: Arc::new(Mutex::new(texture)),
            allocator,
            allocated_shapes: Vec::new(),
        }];

        self.texture_metadatas = texture_metadatas;
        self.free_texture_index = 0;
        self.glyphs = HashMap::new();
    }

    pub fn allocate_glyph(&mut self, c: char) {
        if self.glyphs.contains_key(&c) {
            return;
        }

        let glyph = self.font.glyph(c);
        let allocated_shape =
            if let Some(shape) = glyph.scaled(Scale::uniform(self.font_size as f32)).shape() {
                loop {
                    let allocated_shape = {
                        let texture_allocator =
                            &mut self.texture_metadatas[self.free_texture_index as usize].allocator;
                        AllocatedShape::new(
                            shape.as_slice().into(),
                            texture_allocator,
                            self.shadow_size as f32,
                        )
                    };

                    if let Some(s) = allocated_shape {
                        break Some(s);
                    } else {
                        let (texture, allocator) =
                            Texture::new(self.texture_width, self.texture_height);

                        self.texture_metadatas.push(TextureMetadata {
                            texture: Arc::new(Mutex::new(texture)),
                            allocated_shapes: Vec::new(),
                            allocator,
                        });

                        self.free_texture_index += 1;
                    }
                }
            } else {
                None
            };

        let glyph_info = allocated_shape.map(|allocated_shape| {
            let texture_view = allocated_shape.texture_view.get_view();
            let texture_id = self.free_texture_index;

            self.texture_metadatas[texture_id as usize]
                .allocated_shapes
                .push(allocated_shape);

            GlyphInfo {
                texture_id,
                texture_view: Rect::new(
                    texture_view.min.x as f32 / self.texture_width as f32,
                    texture_view.min.y as f32 / self.texture_height as f32,
                    texture_view.max.x as f32 / self.texture_width as f32,
                    texture_view.max.y as f32 / self.texture_height as f32,
                ),
            }
        });

        self.glyphs.insert(c, glyph_info);
    }

    pub fn allocate_glyphs(&mut self, text: &str) {
        text.chars().for_each(|c| self.allocate_glyph(c));
    }

    pub fn get_texture(&self, texture_id: u32) -> Arc<Mutex<Texture>> {
        self.texture_metadatas[texture_id as usize].texture.clone()
    }

    pub fn get_texture_width(&self) -> u32 {
        self.texture_width
    }

    pub fn get_texture_height(&self) -> u32 {
        self.texture_height
    }

    pub fn set_texture_size(&mut self, width: u32, height: u32) {
        self.texture_width = width;
        self.texture_height = height;
        self.invalidate();
    }

    pub fn get_shadow_size(&self) -> u8 {
        self.shadow_size
    }

    pub fn set_shadow_size(&mut self, shadow_size: u8) {
        self.shadow_size = shadow_size;
        self.invalidate();
    }

    pub fn get_font_size(&self) -> u8 {
        self.font_size
    }

    pub fn set_font_size(&mut self, font_size: u8) {
        self.font_size = font_size;
        self.invalidate();
    }

    pub fn get_ascent(&self) -> f32 {
        let scale = Scale::uniform(1.0);
        let v_metrics = self.font.v_metrics(scale);
        v_metrics.ascent
    }

    pub fn get_descent(&self) -> f32 {
        let scale = Scale::uniform(1.0);
        let v_metrics = self.font.v_metrics(scale);
        v_metrics.descent
    }

    pub fn get_line_gap(&self) -> f32 {
        let scale = Scale::uniform(1.0);
        let v_metrics = self.font.v_metrics(scale);
        v_metrics.line_gap
    }

    pub fn get_texture_render_batches(&mut self) -> Vec<TextureRenderBatch> {
        let mut batches = Vec::new();

        for (texture_id, texture_metadata) in self.texture_metadatas.iter_mut().enumerate() {
            if !texture_metadata.allocated_shapes.is_empty() {
                let allocated_shapes = replace(&mut texture_metadata.allocated_shapes, Vec::new());

                batches.push(TextureRenderBatch {
                    texture_id: texture_id as u32,
                    texture: texture_metadata.texture.clone(),
                    allocated_shapes,
                })
            }
        }

        batches
    }

    pub fn layout_text_block(&mut self, text: &str) -> TextBlockLayout {
        self.allocate_glyphs(text);

        let mut glyph_layouts = Vec::new();

        let mut bb_min_x = 0.0;
        let mut bb_min_y = 0.0;
        let mut bb_max_x = 0.0;
        let mut bb_max_y = 0.0;

        let shadow = self.shadow_size as f32 / self.font_size as f32;
        let scale = Scale::uniform(1.0);
        let v_metrics = self.font.v_metrics(scale);

        let mut last_glyph = None;
        let mut offset_x = 0.0;
        let mut offset_y = 0.0;

        for c in text.chars() {
            if c == '\n' {
                offset_x = 0.0;
                last_glyph = None;
                offset_y -= v_metrics.ascent - v_metrics.descent + v_metrics.line_gap;
                continue;
            }

            let glyph = self.font.glyph(c).scaled(scale);
            let glyph_info = self.glyphs.get(&c).unwrap();

            if let Some(last_glyph) = last_glyph {
                offset_x += self.font.pair_kerning(scale, last_glyph, glyph.id());
            }

            let advance_width = glyph.h_metrics().advance_width;

            if let Some(bb) = glyph.exact_bounding_box() {
                let min_x = offset_x + bb.min.x;
                let min_y = offset_y - bb.max.y;
                let max_x = offset_x + bb.max.x;
                let max_y = offset_y - bb.min.y;

                bb_min_x = min_x.min(bb_min_x);
                bb_min_y = min_y.min(bb_min_y);
                bb_max_x = max_x.max(bb_max_x);
                bb_max_y = max_y.max(bb_max_y);

                if let Some(glyph_info) = glyph_info {
                    glyph_layouts.push(GlyphLayout {
                        texture_id: glyph_info.texture_id,
                        screen_coord: Rect::new(
                            min_x - shadow,
                            min_y - shadow,
                            max_x + shadow,
                            max_y + shadow,
                        ),
                        texture_coord: glyph_info.texture_view,
                    });
                }
            }

            offset_x += advance_width;
            last_glyph = Some(glyph.id());
        }

        TextBlockLayout {
            font_size: self.font_size,
            shadow_size: self.shadow_size,
            bounding_box: Rect::new(bb_min_x, bb_min_y, bb_max_x, bb_max_y),
            glyph_layouts,
        }
    }
}

impl<'a> From<&'a [Contour]> for Shape {
    fn from(contours: &'a [Contour]) -> Shape {
        let segments = contours.iter().flat_map(|contour| {
            once(Segment::Start {
                count: contour.segments.len(),
            })
            .chain(contour.segments.iter().map(|segment| match segment {
                FontSegment::Line(line) => Segment::Line {
                    line: Line {
                        p0: Point2::new(line.p[0].x, line.p[0].y),
                        p1: Point2::new(line.p[1].x, line.p[1].y),
                    },
                },
                FontSegment::Curve(curve) => Segment::Curve {
                    curve: Curve {
                        p0: Point2::new(curve.p[0].x, curve.p[0].y),
                        p1: Point2::new(curve.p[1].x, curve.p[1].y),
                        p2: Point2::new(curve.p[2].x, curve.p[2].y),
                    },
                },
            }))
        });

        Shape::from_iter(segments)
    }
}