codesnap 0.13.2

Pure Rust library for generating beautiful code snapshots
Documentation
use std::sync::Arc;

use cosmic_text::{
    fontdb::Source, Align, Attrs, AttrsList, Buffer, BufferLine, Color, Family, FontSystem,
    LayoutRunIter, LineEnding, Metrics, Shaping, SwashCache,
};
use tiny_skia::{Paint, Pixmap, Rect, Transform};

const CASKAYDIA_COVE_NERD_FONT: &[u8] =
    include_bytes!("../../assets/fonts/CaskaydiaCoveNerdFont-Regular.ttf");
const CASKAYDIA_COVE_BOLD_NERD_FONT: &[u8] =
    include_bytes!("../../assets/fonts/CaskaydiaCoveNerdFont-Bold.ttf");
const CASKAYDIA_COVE_ITALIC_NERD_FONT: &[u8] =
    include_bytes!("../../assets/fonts/CaskaydiaCoveNerdFont-Italic.ttf");

const PACIFICO_FONT: &[u8] = include_bytes!("../../assets/fonts/Pacifico-Regular.ttf");

pub struct FontRenderer {
    font_system: FontSystem,
    scale_factor: f32,
}

fn get_default_attrs<'a>() -> Attrs<'a> {
    Attrs::new().family(Family::Name("CaskaydiaCove Nerd Font"))
}

impl FontRenderer {
    pub fn new(scale_factor: f32, fonts_folders: Vec<String>) -> FontRenderer {
        let fonts_data: Vec<&[u8]> = vec![
            CASKAYDIA_COVE_NERD_FONT,
            PACIFICO_FONT,
            CASKAYDIA_COVE_BOLD_NERD_FONT,
            CASKAYDIA_COVE_ITALIC_NERD_FONT,
        ];
        let sources = fonts_data
            .into_iter()
            .map(|data| Source::Binary(Arc::new(data)));

        let mut font_system: FontSystem = FontSystem::new_with_fonts(sources);
        let font_db = font_system.db_mut();

        for folder in fonts_folders {
            font_db.load_fonts_dir(folder);
        }

        FontRenderer {
            font_system,
            scale_factor,
        }
    }

    pub fn measure_text(&mut self, metrics: Metrics, text: &str) -> (f32, f32) {
        let mut buffer = Buffer::new(&mut self.font_system, metrics.scale(self.scale_factor));

        buffer.set_text(
            &mut self.font_system,
            text,
            &get_default_attrs(),
            Shaping::Advanced,
        );

        let layout_runs: LayoutRunIter = buffer.layout_runs();
        let line_height = buffer.lines.len() as f32 * buffer.metrics().line_height;
        let mut_width = layout_runs.fold(0f32, |max_width, run| max_width.max(run.line_w));

        (
            mut_width.ceil() / self.scale_factor,
            line_height / self.scale_factor,
        )
    }

    pub fn draw_text(
        &mut self,
        x: f32,
        y: f32,
        metrics: Metrics,
        spans: Vec<(&str, Attrs)>,
        pixmap: &mut Pixmap,
    ) {
        let mut buffer = Buffer::new(&mut self.font_system, metrics.scale(self.scale_factor));

        buffer.set_rich_text(
            &mut self.font_system,
            spans,
            &get_default_attrs(),
            Shaping::Advanced,
            None,
        );

        self.draw(x, y, &mut buffer, pixmap);
    }

    pub fn draw_line(
        &mut self,
        x: f32,
        y: f32,
        metrics: Metrics,
        line: &str,
        attrs: Attrs,
        align: Option<Align>,
        pixmap: &mut Pixmap,
    ) {
        let mut buffer = Buffer::new(&mut self.font_system, metrics.scale(self.scale_factor));
        let attrs_list = AttrsList::new(&attrs);
        let mut line = if cfg!(windows) {
            BufferLine::new(line, LineEnding::CrLf, attrs_list, Shaping::Advanced)
        } else {
            BufferLine::new(line, LineEnding::Lf, attrs_list, Shaping::Advanced)
        };

        line.set_align(align);
        buffer.lines = vec![line];
        buffer.set_size(
            &mut self.font_system,
            Some(pixmap.width() as f32),
            Some(pixmap.height() as f32),
        );
        self.draw(x, y, &mut buffer, pixmap);
    }

    fn draw<'a>(&mut self, x: f32, y: f32, buffer: &mut Buffer, pixmap: &mut Pixmap) {
        let mut swash_cache = SwashCache::new();
        let default_font_color = Color::rgb(255, 255, 255);

        buffer.draw(
            &mut self.font_system,
            &mut swash_cache,
            default_font_color,
            |font_x, font_y, w, h, color| {
                let mut paint = Paint {
                    anti_alias: true,
                    ..Default::default()
                };

                paint.set_color_rgba8(color.r(), color.g(), color.b(), color.a());

                let rect = Rect::from_xywh(
                    font_x as f32 + x * self.scale_factor,
                    font_y as f32 + y * self.scale_factor,
                    w as f32,
                    h as f32,
                )
                .expect("Cannot draw text on pixmap");

                pixmap.fill_rect(rect, &paint, Transform::identity(), None);
            },
        );
    }
}