Skip to content

Commit

Permalink
refactor: add TextLineDrawer trait
Browse files Browse the repository at this point in the history
  • Loading branch information
Aloxaf committed Mar 4, 2024
1 parent a460f0a commit 319066e
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 59 deletions.
3 changes: 2 additions & 1 deletion src/bin/silicon/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -269,7 +270,7 @@ impl Config {
}
}

pub fn get_formatter(&self) -> Result<ImageFormatter, Error> {
pub fn get_formatter(&self) -> Result<ImageFormatter<FontCollection>, Error> {
let formatter = ImageFormatterBuilder::new()
.line_pad(self.line_pad)
.window_controls(!self.no_window_controls)
Expand Down
1 change: 1 addition & 0 deletions src/bin/silicon/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down
61 changes: 53 additions & 8 deletions src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,54 @@ 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;
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<u8>,
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<u8>,
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 {
Expand Down Expand Up @@ -172,11 +212,15 @@ impl ImageFont {
///
/// It can be used to draw text on the image.
#[derive(Debug)]
pub struct FontCollection(Vec<ImageFont>);
pub struct FontCollection {
fonts: Vec<ImageFont>,
}

impl Default for FontCollection {
fn default() -> Self {
Self(vec![ImageFont::default()])
Self {
fonts: vec![ImageFont::default()],
}
}
}

Expand All @@ -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));
Expand All @@ -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()
Expand Down Expand Up @@ -350,9 +394,9 @@ impl FontCollection {
I: GenericImage,
<I::Pixel as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
{
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);

Expand All @@ -372,6 +416,7 @@ impl FontCollection {
}
}

#[derive(Debug)]
struct PositionedGlyph {
id: u32,
font: Font,
Expand Down
60 changes: 30 additions & 30 deletions src/formatter.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
/// pad between lines
/// Default: 2
line_pad: u32,
Expand Down Expand Up @@ -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<u32>,
/// Shadow adder
Expand Down Expand Up @@ -151,7 +151,7 @@ impl<S: AsRef<str> + Default> ImageFormatterBuilder<S> {
self
}

pub fn build(self) -> Result<ImageFormatter, FontError> {
pub fn build(self) -> Result<ImageFormatter<FontCollection>, FontError> {
let font = if self.font.is_empty() {
FontCollection::default()
} else {
Expand Down Expand Up @@ -191,38 +191,38 @@ struct Drawable {
drawables: Vec<(u32, u32, Option<Color>, FontStyle, String)>,
}

impl ImageFormatter {
impl<T: TextLineDrawer> ImageFormatter<T> {
/// 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,
)
}

/// 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![];
Expand All @@ -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);
}
Expand All @@ -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
Expand All @@ -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(),
Expand All @@ -283,7 +283,7 @@ impl ImageFormatter {
}
}

fn draw_line_number(&self, image: &mut DynamicImage, lineno: u32, mut color: Rgba<u8>) {
fn draw_line_number(&mut self, image: &mut RgbaImage, lineno: u32, mut color: Rgba<u8>) {
for i in color.0.iter_mut() {
*i = (*i).saturating_sub(20);
}
Expand All @@ -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<I: IntoIterator<Item = u32>>(&self, image: &mut DynamicImage, lines: I) {
fn highlight_lines<I: IntoIterator<Item = u32>>(&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;
Expand All @@ -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::<Vec<_>>();
self.highlight_lines(&mut image, highlight_lines);
}
if self.line_number {
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit 319066e

Please sign in to comment.