From 319066e9a1d4424c7b1f27bc6ea6205ad340a289 Mon Sep 17 00:00:00 2001 From: Aloxaf Date: Sat, 2 Mar 2024 13:37:51 +0800 Subject: [PATCH] refactor: add TextLineDrawer trait --- src/bin/silicon/config.rs | 3 +- src/bin/silicon/main.rs | 1 + src/font.rs | 61 ++++++++++++++++++++++++++++++++++----- src/formatter.rs | 60 +++++++++++++++++++------------------- src/utils.rs | 30 +++++++------------ 5 files changed, 96 insertions(+), 59 deletions(-) diff --git a/src/bin/silicon/config.rs b/src/bin/silicon/config.rs index 5f76a06..13e40d0 100644 --- a/src/bin/silicon/config.rs +++ b/src/bin/silicon/config.rs @@ -2,6 +2,7 @@ use anyhow::{Context, Error}; use clipboard::{ClipboardContext, ClipboardProvider}; use image::Rgba; use silicon::directories::PROJECT_DIRS; +use silicon::font::FontCollection; use silicon::formatter::{ImageFormatter, ImageFormatterBuilder}; use silicon::utils::{Background, ShadowAdder, ToRgba}; use std::ffi::OsString; @@ -269,7 +270,7 @@ impl Config { } } - pub fn get_formatter(&self) -> Result { + pub fn get_formatter(&self) -> Result, Error> { let formatter = ImageFormatterBuilder::new() .line_pad(self.line_pad) .window_controls(!self.no_window_controls) diff --git a/src/bin/silicon/main.rs b/src/bin/silicon/main.rs index 93dff16..6591555 100644 --- a/src/bin/silicon/main.rs +++ b/src/bin/silicon/main.rs @@ -148,6 +148,7 @@ fn run() -> Result<(), Error> { let mut formatter = config.get_formatter()?; let image = formatter.format(&highlight, &theme); + let image = DynamicImage::ImageRgba8(image); if config.to_clipboard { dump_image_to_clipboard(&image)?; diff --git a/src/font.rs b/src/font.rs index 7f8e43c..57c7b01 100644 --- a/src/font.rs +++ b/src/font.rs @@ -21,7 +21,7 @@ use font_kit::font::Font; use font_kit::hinting::HintingOptions; use font_kit::properties::{Properties, Style, Weight}; use font_kit::source::SystemSource; -use image::{GenericImage, Pixel}; +use image::{GenericImage, Pixel, Rgba, RgbaImage}; use imageproc::definitions::Clamp; use imageproc::pixelops::weighted_sum; use pathfinder_geometry::transform2d::Transform2F; @@ -29,6 +29,46 @@ use std::collections::HashMap; use std::sync::Arc; use syntect::highlighting; +/// a single line text drawer +pub trait TextLineDrawer { + /// get the height of the text + fn height(&mut self, text: &str) -> u32; + /// get the width of the text + fn width(&mut self, text: &str) -> u32; + /// draw the text + fn draw_text( + &mut self, + image: &mut RgbaImage, + color: Rgba, + x: u32, + y: u32, + font_style: FontStyle, + text: &str, + ); +} + +impl TextLineDrawer for FontCollection { + fn height(&mut self, _text: &str) -> u32 { + self.get_font_height() + } + + fn width(&mut self, text: &str) -> u32 { + self.layout(text, REGULAR).1 + } + + fn draw_text( + &mut self, + image: &mut RgbaImage, + color: Rgba, + x: u32, + y: u32, + font_style: FontStyle, + text: &str, + ) { + self.draw_text_mut(image, color, x, y, font_style, text); + } +} + /// Font style #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub enum FontStyle { @@ -172,11 +212,15 @@ impl ImageFont { /// /// It can be used to draw text on the image. #[derive(Debug)] -pub struct FontCollection(Vec); +pub struct FontCollection { + fonts: Vec, +} impl Default for FontCollection { fn default() -> Self { - Self(vec![ImageFont::default()]) + Self { + fonts: vec![ImageFont::default()], + } } } @@ -191,11 +235,11 @@ impl FontCollection { Err(err) => eprintln!("[error] Error occurs when load font `{}`: {}", name, err), } } - Ok(Self(fonts)) + Ok(Self { fonts }) } fn glyph_for_char(&self, c: char, style: FontStyle) -> Option<(u32, &ImageFont, &Font)> { - for font in &self.0 { + for font in &self.fonts { let result = font.get_by_style(style); if let Some(id) = result.glyph_for_char(c) { return Some((id, font, result)); @@ -207,7 +251,7 @@ impl FontCollection { /// get max height of all the fonts pub fn get_font_height(&self) -> u32 { - self.0 + self.fonts .iter() .map(|font| font.get_font_height()) .max() @@ -350,9 +394,9 @@ impl FontCollection { I: GenericImage, ::Subpixel: ValueInto + Clamp, { - let metrics = self.0[0].get_regular().metrics(); + let metrics = self.fonts[0].get_regular().metrics(); let offset = - (metrics.descent / metrics.units_per_em as f32 * self.0[0].size).round() as i32; + (metrics.descent / metrics.units_per_em as f32 * self.fonts[0].size).round() as i32; let (glyphs, width) = self.layout(text, style); @@ -372,6 +416,7 @@ impl FontCollection { } } +#[derive(Debug)] struct PositionedGlyph { id: u32, font: Font, diff --git a/src/formatter.rs b/src/formatter.rs index 7182edc..72f7597 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -1,11 +1,11 @@ //! Format the output of syntect into an image use crate::error::FontError; -use crate::font::{FontCollection, FontStyle}; +use crate::font::{FontCollection, FontStyle, TextLineDrawer}; use crate::utils::*; -use image::{DynamicImage, GenericImageView, Rgba, RgbaImage}; +use image::{Rgba, RgbaImage}; use syntect::highlighting::{Color, Style, Theme}; -pub struct ImageFormatter { +pub struct ImageFormatter { /// pad between lines /// Default: 2 line_pad: u32, @@ -42,7 +42,7 @@ pub struct ImageFormatter { line_number_chars: u32, /// font of english character, should be mono space font /// Default: Hack (builtin) - font: FontCollection, + font: T, /// Highlight lines highlight_lines: Vec, /// Shadow adder @@ -151,7 +151,7 @@ impl + Default> ImageFormatterBuilder { self } - pub fn build(self) -> Result { + pub fn build(self) -> Result, FontError> { let font = if self.font.is_empty() { FontCollection::default() } else { @@ -191,19 +191,19 @@ struct Drawable { drawables: Vec<(u32, u32, Option, FontStyle, String)>, } -impl ImageFormatter { +impl ImageFormatter { /// calculate the height of a line - fn get_line_height(&self) -> u32 { - self.font.get_font_height() + self.line_pad + fn get_line_height(&mut self) -> u32 { + self.font.height(" ") + self.line_pad } /// calculate the Y coordinate of a line - fn get_line_y(&self, lineno: u32) -> u32 { + fn get_line_y(&mut self, lineno: u32) -> u32 { lineno * self.get_line_height() + self.code_pad + self.code_pad_top } /// calculate the size of code area - fn get_image_size(&self, max_width: u32, lineno: u32) -> (u32, u32) { + fn get_image_size(&mut self, max_width: u32, lineno: u32) -> (u32, u32) { ( (max_width + self.code_pad).max(150), self.get_line_y(lineno + 1) + self.code_pad, @@ -211,18 +211,18 @@ impl ImageFormatter { } /// Calculate where code start - fn get_left_pad(&self) -> u32 { + fn get_left_pad(&mut self) -> u32 { self.code_pad + if self.line_number { let tmp = format!("{:>width$}", 0, width = self.line_number_chars as usize); - 2 * self.line_number_pad + self.font.get_text_len(&tmp) + 2 * self.line_number_pad + self.font.width(&tmp) } else { 0 } } /// create - fn create_drawables(&self, v: &[Vec<(Style, &str)>]) -> Drawable { + fn create_drawables(&mut self, v: &[Vec<(Style, &str)>]) -> Drawable { // tab should be replaced to whitespace so that it can be rendered correctly let tab = " ".repeat(self.tab_width as usize); let mut drawables = vec![]; @@ -246,7 +246,7 @@ impl ImageFormatter { text.to_owned(), )); - width += self.font.get_text_len(&text); + width += self.font.width(&text); max_width = max_width.max(width); } @@ -255,7 +255,7 @@ impl ImageFormatter { if self.window_title.is_some() { let title = self.window_title.as_ref().unwrap(); - let title_width = self.font.get_text_len(title); + let title_width = self.font.width(title); let ctrls_offset = if self.window_controls { self.window_controls_width + self.title_bar_pad @@ -266,7 +266,7 @@ impl ImageFormatter { drawables.push(( ctrls_offset + self.title_bar_pad, - self.title_bar_pad + ctrls_center - self.font.get_font_height() / 2, + self.title_bar_pad + ctrls_center - self.font.height(" ") / 2, None, FontStyle::BOLD, title.to_string(), @@ -283,7 +283,7 @@ impl ImageFormatter { } } - fn draw_line_number(&self, image: &mut DynamicImage, lineno: u32, mut color: Rgba) { + fn draw_line_number(&mut self, image: &mut RgbaImage, lineno: u32, mut color: Rgba) { for i in color.0.iter_mut() { *i = (*i).saturating_sub(20); } @@ -293,36 +293,37 @@ impl ImageFormatter { i + self.line_offset, width = self.line_number_chars as usize ); - self.font.draw_text_mut( + let y = self.get_line_y(i); + self.font.draw_text( image, color, self.code_pad, - self.get_line_y(i), + y, FontStyle::REGULAR, &line_number, ); } } - fn highlight_lines>(&self, image: &mut DynamicImage, lines: I) { + fn highlight_lines>(&mut self, image: &mut RgbaImage, lines: I) { let width = image.width(); - let height = self.font.get_font_height() + self.line_pad; - let mut color = image.get_pixel(20, 20); + let height = self.get_line_height(); + let color = image.get_pixel_mut(20, 20); for i in color.0.iter_mut() { *i = (*i).saturating_add(40); } - let shadow = RgbaImage::from_pixel(width, height, color); + let shadow = RgbaImage::from_pixel(width, height, *color); for i in lines { let y = self.get_line_y(i - 1); - copy_alpha(&shadow, image.as_mut_rgba8().unwrap(), 0, y); + copy_alpha(&shadow, image, 0, y); } } // TODO: use &T instead of &mut T ? - pub fn format(&mut self, v: &[Vec<(Style, &str)>], theme: &Theme) -> DynamicImage { + pub fn format(&mut self, v: &[Vec<(Style, &str)>], theme: &Theme) -> RgbaImage { if self.line_number { self.line_number_chars = (((v.len() + self.line_offset as usize) as f32).log10() + 1.0).floor() as u32; @@ -338,15 +339,15 @@ impl ImageFormatter { let foreground = theme.settings.foreground.unwrap(); let background = theme.settings.background.unwrap(); - let mut image = - DynamicImage::ImageRgba8(RgbaImage::from_pixel(size.0, size.1, background.to_rgba())); + let mut image = RgbaImage::from_pixel(size.0, size.1, background.to_rgba()); if !self.highlight_lines.is_empty() { let highlight_lines = self .highlight_lines .iter() .cloned() - .filter(|&n| n >= 1 && n <= drawables.max_lineno + 1); + .filter(|&n| n >= 1 && n <= drawables.max_lineno + 1) + .collect::>(); self.highlight_lines(&mut image, highlight_lines); } if self.line_number { @@ -355,8 +356,7 @@ impl ImageFormatter { for (x, y, color, style, text) in drawables.drawables { let color = color.unwrap_or(foreground).to_rgba(); - self.font - .draw_text_mut(&mut image, color, x, y, style, &text); + self.font.draw_text(&mut image, color, x, y, style, &text); } if self.window_controls { diff --git a/src/utils.rs b/src/utils.rs index 7a27824..124e968 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,7 @@ use crate::error::ParseColorError; use image::imageops::{crop_imm, resize, FilterType}; use image::Pixel; -use image::{DynamicImage, GenericImage, GenericImageView, Rgba, RgbaImage}; +use image::{GenericImage, GenericImageView, Rgba, RgbaImage}; use imageproc::drawing::{draw_filled_rect_mut, draw_line_segment_mut}; use imageproc::rect::Rect; @@ -74,17 +74,17 @@ pub struct WindowControlsParams { } /// Add the window controls for image -pub(crate) fn add_window_controls(image: &mut DynamicImage, params: &WindowControlsParams) { +pub(crate) fn add_window_controls(image: &mut RgbaImage, params: &WindowControlsParams) { let color = [ ("#FF5F56", "#E0443E"), ("#FFBD2E", "#DEA123"), ("#27C93F", "#1AAB29"), ]; - let mut background = image.get_pixel(37, 37); + let background = image.get_pixel_mut(37, 37); background.0[3] = 0; - let mut title_bar = RgbaImage::from_pixel(params.width * 3, params.height * 3, background); + let mut title_bar = RgbaImage::from_pixel(params.width * 3, params.height * 3, *background); let step = (params.radius * 2) as i32; let spacer = step * 2; let center_y = (params.height / 2) as i32; @@ -112,12 +112,7 @@ pub(crate) fn add_window_controls(image: &mut DynamicImage, params: &WindowContr FilterType::Triangle, ); - copy_alpha( - &title_bar, - image.as_mut_rgba8().unwrap(), - params.padding, - params.padding, - ); + copy_alpha(&title_bar, image, params.padding, params.padding); } #[derive(Clone, Debug)] @@ -204,7 +199,7 @@ impl ShadowAdder { self } - pub fn apply_to(&self, image: &DynamicImage) -> DynamicImage { + pub fn apply_to(&self, image: &RgbaImage) -> RgbaImage { // the size of the final image let width = image.width() + self.pad_horiz * 2; let height = image.height() + self.pad_vert * 2; @@ -226,14 +221,9 @@ impl ShadowAdder { // shadow = blur(&shadow, self.blur_radius); // copy the original image to the top of it - copy_alpha( - image.as_rgba8().unwrap(), - &mut shadow, - self.pad_horiz, - self.pad_vert, - ); + copy_alpha(image, &mut shadow, self.pad_horiz, self.pad_vert); - DynamicImage::ImageRgba8(shadow) + shadow } } @@ -266,7 +256,7 @@ pub(crate) fn copy_alpha(src: &RgbaImage, dst: &mut RgbaImage, x: u32, y: u32) { } /// Round the corner of the image -pub(crate) fn round_corner(image: &mut DynamicImage, radius: u32) { +pub(crate) fn round_corner(image: &mut RgbaImage, radius: u32) { // draw a circle with given foreground on given background // then split it into four pieces and paste them to the four corner of the image // @@ -287,7 +277,7 @@ pub(crate) fn round_corner(image: &mut DynamicImage, radius: u32) { &mut circle, (((radius + 1) * 2) as i32, ((radius + 1) * 2) as i32), radius as i32 * 2, - foreground, + *foreground, ); // scale down the circle to the correct size