mirror of
https://github.com/mistricky/codesnap.nvim.git
synced 2025-01-28 11:47:29 -08:00
[Refactor] refactor use codesnap library
This commit is contained in:
parent
f74be234df
commit
191c1adc9b
35 changed files with 473 additions and 2178 deletions
|
@ -9,3 +9,9 @@ rustflags = [
|
|||
"-C", "link-arg=-undefined",
|
||||
"-C", "link-arg=dynamic_lookup",
|
||||
]
|
||||
|
||||
[target.x86_64-unknown-linux-musl]
|
||||
rustflags = ["-C", "target-feature=-crt-static"]
|
||||
|
||||
[target.aarch64-unknown-linux-musl]
|
||||
rustflags = ["-C", "target-feature=-crt-static"]
|
||||
|
|
630
generator/Cargo.lock
generated
630
generator/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -3,17 +3,11 @@ name = "generator"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
nvim-oxi = { features = ["neovim-0-9", "libuv"], version = "0.5.1" }
|
||||
tiny-skia = "0.11.4"
|
||||
syntect = "5.2.0"
|
||||
cosmic-text = "0.12.0"
|
||||
serde = "1.0.204"
|
||||
arboard = { features = ["wayland-data-control"], version = "3.4.0" }
|
||||
thiserror = "1.0.63"
|
||||
regex = "1.10.5"
|
||||
two-face = "0.4.0"
|
||||
cached = "0.53.1"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
codesnap = "0.7.1"
|
||||
mlua = { version = "0.10.0", features = ["module", "luajit", "serialize"] }
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
use cached::proc_macro::cached;
|
||||
use regex::Regex;
|
||||
|
||||
const MIN_WIDTH: f32 = 100.;
|
||||
pub const CHAR_WIDTH: f32 = 9.05;
|
||||
|
||||
fn min_width(width: f32) -> f32 {
|
||||
if width < MIN_WIDTH {
|
||||
MIN_WIDTH
|
||||
} else {
|
||||
width
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calc_max_line_number_length(code_length: usize, start_line_number: usize) -> usize {
|
||||
let max_line_number = code_length + start_line_number;
|
||||
|
||||
// If code length is 1, the max_line_number will equal to start_line_number
|
||||
(max_line_number - 1).to_string().len()
|
||||
}
|
||||
|
||||
pub fn calc_wh(text: &str, char_wdith: f32, line_height: f32) -> (f32, f32) {
|
||||
let trimmed_text = prepare_code(text);
|
||||
let lines = trimmed_text.lines();
|
||||
let max_length_line = lines.clone().into_iter().fold("", |max_length_line, cur| {
|
||||
if cur.len() > max_length_line.len() {
|
||||
cur
|
||||
} else {
|
||||
max_length_line
|
||||
}
|
||||
});
|
||||
let width = max_length_line.len() as f32 * char_wdith;
|
||||
let height = lines.collect::<Vec<&str>>().len() as f32 * line_height;
|
||||
|
||||
(width, height)
|
||||
}
|
||||
|
||||
// Because the code block is input by users, we need to calculate the width and height
|
||||
// to make sure render the width and height of the "editor" shape correctly
|
||||
#[cached(key = "String", convert = r#"{ format!("{}", text) }"#)]
|
||||
pub fn calc_wh_with_min_width(text: &str, char_wdith: f32, line_height: f32) -> (f32, f32) {
|
||||
let (width, height) = calc_wh(text, char_wdith, line_height);
|
||||
|
||||
(min_width(width), height)
|
||||
}
|
||||
|
||||
// The tab character is incorrectly render using cosmic, need to replace all tab with space
|
||||
// before render the code
|
||||
fn replace_tab_to_space(text: &str) -> String {
|
||||
let spaces = " ".repeat(2);
|
||||
|
||||
str::replace(text, "\t", &spaces)
|
||||
}
|
||||
|
||||
// Find min indention of code lines, and remove the same indention from subsequent lines
|
||||
fn trim_space(text: &str) -> String {
|
||||
let lines = text.split("\n").collect::<Vec<&str>>();
|
||||
let regex = Regex::new(r"(?:^|\n)(\s*)").unwrap();
|
||||
let captures_iter = regex.captures_iter(text);
|
||||
let space_lengths = captures_iter
|
||||
.map(|capture| capture.get(1).unwrap().as_str().len())
|
||||
.collect::<Vec<usize>>();
|
||||
|
||||
if space_lengths.len() < lines.len() {
|
||||
return text.to_string();
|
||||
}
|
||||
|
||||
let need_to_remove_spaces = " ".repeat(space_lengths.into_iter().min().unwrap());
|
||||
|
||||
lines
|
||||
.into_iter()
|
||||
.map(|line| {
|
||||
Regex::new(format!("^{}", need_to_remove_spaces).as_ref())
|
||||
.unwrap()
|
||||
.replace(line, "")
|
||||
.to_string()
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
pub fn prepare_code(code: &str) -> String {
|
||||
trim_space(&replace_tab_to_space(&code))
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
use std::i64;
|
||||
use tiny_skia::Color;
|
||||
|
||||
const HEX_COLOR_LENGTH: usize = 7;
|
||||
const HEX_COLOR_WITH_ALPHA_LENGTH: usize = 9;
|
||||
|
||||
pub struct RgbaColor {
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
pub fn is_valid_hex_color(color: &str) -> bool {
|
||||
(color.len() == HEX_COLOR_LENGTH || color.len() == HEX_COLOR_WITH_ALPHA_LENGTH)
|
||||
&& color.starts_with("#")
|
||||
}
|
||||
|
||||
fn parse_color_to_rgba_hex(hex: &str) -> String {
|
||||
if !is_valid_hex_color(&hex) || hex.len() == HEX_COLOR_WITH_ALPHA_LENGTH {
|
||||
hex.to_string()
|
||||
} else {
|
||||
format!("{}ff", hex)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<RgbaColor> for String {
|
||||
fn into(self) -> RgbaColor {
|
||||
let rgba_hex_color = parse_color_to_rgba_hex(&self);
|
||||
// Remove the '#' symbol
|
||||
let hex_color = &rgba_hex_color.to_lowercase()[1..rgba_hex_color.len()];
|
||||
let chars = hex_color.chars().collect::<Vec<char>>();
|
||||
let splits = &chars
|
||||
.chunks(2)
|
||||
.map(|chunk| i64::from_str_radix(&chunk.iter().collect::<String>(), 16).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
RgbaColor {
|
||||
color: Color::from_rgba8(
|
||||
splits[0] as u8,
|
||||
splits[1] as u8,
|
||||
splits[2] as u8,
|
||||
splits[3] as u8,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
pub mod background;
|
||||
pub mod breadcrumbs;
|
||||
pub mod code_block;
|
||||
pub mod container;
|
||||
pub mod editor;
|
||||
pub mod highlight_code_block;
|
||||
pub mod interface;
|
||||
pub mod line_number;
|
||||
pub mod rect;
|
||||
pub mod watermark;
|
|
@ -1,147 +0,0 @@
|
|||
use tiny_skia::{
|
||||
Color, GradientStop, LinearGradient, Paint, Pixmap, Point, Rect, SpreadMode, Transform,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
color::{is_valid_hex_color, RgbaColor},
|
||||
edges::{edge::Edge, padding::Padding},
|
||||
};
|
||||
|
||||
use super::interface::{
|
||||
component::{Component, ComponentContext, RenderParams},
|
||||
render_error::{self, RenderError},
|
||||
style::{ComponentAlign, ComponentStyle, RawComponentStyle},
|
||||
};
|
||||
|
||||
pub struct Background {
|
||||
children: Vec<Box<dyn Component>>,
|
||||
padding: Padding,
|
||||
}
|
||||
|
||||
impl Background {
|
||||
pub fn new(padding: Padding, children: Vec<Box<dyn Component>>) -> Background {
|
||||
Background { children, padding }
|
||||
}
|
||||
|
||||
pub fn parse_background_padding(
|
||||
horizontal_background_padding: f32,
|
||||
vertical_background_padding: f32,
|
||||
background_padding: Option<f32>,
|
||||
) -> Padding {
|
||||
match background_padding {
|
||||
Some(padding) => Padding::from_value(padding),
|
||||
None => Padding {
|
||||
top: vertical_background_padding,
|
||||
bottom: vertical_background_padding,
|
||||
left: horizontal_background_padding,
|
||||
right: horizontal_background_padding,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_background(padding: &Padding) -> bool {
|
||||
return padding.horizontal() != 0. || padding.vertical() != 0.;
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Background {
|
||||
fn children(&self) -> &Vec<Box<dyn Component>> {
|
||||
&self.children
|
||||
}
|
||||
|
||||
fn style(&self) -> RawComponentStyle {
|
||||
RawComponentStyle::default()
|
||||
.align(ComponentAlign::Column)
|
||||
.padding(self.padding.clone())
|
||||
}
|
||||
|
||||
fn self_render_condition(&self) -> bool {
|
||||
Self::has_background(&self.padding)
|
||||
}
|
||||
|
||||
fn draw_self(
|
||||
&self,
|
||||
pixmap: &mut Pixmap,
|
||||
context: &ComponentContext,
|
||||
_render_params: &RenderParams,
|
||||
_style: &ComponentStyle,
|
||||
_parent_style: &ComponentStyle,
|
||||
) -> render_error::Result<()> {
|
||||
let mut paint = Paint::default();
|
||||
let w = pixmap.width() as f32;
|
||||
let h = pixmap.height() as f32;
|
||||
let params = &context.take_snapshot_params;
|
||||
|
||||
paint.anti_alias = false;
|
||||
match params.bg_color.as_ref() {
|
||||
Some(color) => {
|
||||
if !is_valid_hex_color(color) {
|
||||
return Err(RenderError::InvalidHexColor(color.to_string()));
|
||||
}
|
||||
|
||||
let rgba_color: RgbaColor = color.to_string().into();
|
||||
|
||||
paint.set_color(rgba_color.color);
|
||||
}
|
||||
None => {
|
||||
paint.shader = LinearGradient::new(
|
||||
Point::from_xy(0., 0.),
|
||||
Point::from_xy(w, 0.),
|
||||
Background::get_theme(&context.take_snapshot_params.bg_theme)?,
|
||||
SpreadMode::Pad,
|
||||
Transform::identity(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
pixmap.fill_rect(
|
||||
Rect::from_xywh(0., 0., w, h).unwrap(),
|
||||
&paint,
|
||||
Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Background {
|
||||
fn get_theme(theme: &str) -> render_error::Result<Vec<GradientStop>> {
|
||||
let theme = match theme {
|
||||
"default" => vec![
|
||||
GradientStop::new(0.0, Color::from_rgba8(58, 28, 113, 255)),
|
||||
GradientStop::new(0.5, Color::from_rgba8(215, 109, 119, 255)),
|
||||
GradientStop::new(0.95, Color::from_rgba8(255, 175, 123, 255)),
|
||||
],
|
||||
"sea" => vec![
|
||||
GradientStop::new(0.0, Color::from_rgba8(31, 162, 255, 255)),
|
||||
GradientStop::new(0.4, Color::from_rgba8(18, 216, 250, 255)),
|
||||
GradientStop::new(0.95, Color::from_rgba8(166, 255, 203, 255)),
|
||||
],
|
||||
"grape" => vec![
|
||||
GradientStop::new(0.28, Color::from_rgba8(103, 90, 247, 255)),
|
||||
GradientStop::new(0.95, Color::from_rgba8(189, 101, 250, 255)),
|
||||
],
|
||||
"peach" => vec![
|
||||
GradientStop::new(0.22, Color::from_rgba8(221, 94, 137, 255)),
|
||||
GradientStop::new(0.95, Color::from_rgba8(247, 187, 151, 255)),
|
||||
],
|
||||
"summer" => vec![
|
||||
GradientStop::new(0.28, Color::from_rgba8(248, 165, 194, 255)),
|
||||
GradientStop::new(0.95, Color::from_rgba8(116, 185, 255, 255)),
|
||||
],
|
||||
"bamboo" => vec![
|
||||
GradientStop::new(0.22, Color::from_rgba8(107, 203, 165, 255)),
|
||||
GradientStop::new(0.95, Color::from_rgba8(202, 244, 194, 255)),
|
||||
],
|
||||
"dusk" => vec![
|
||||
GradientStop::new(0.22, Color::from_rgba8(255, 98, 110, 255)),
|
||||
GradientStop::new(0.95, Color::from_rgba8(255, 190, 113, 255)),
|
||||
],
|
||||
_ => return Err(RenderError::UnknownBackgroundTheme(theme.to_string())),
|
||||
};
|
||||
|
||||
Ok(theme)
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
use cosmic_text::{Attrs, Color, Family};
|
||||
use regex::Regex;
|
||||
|
||||
use crate::{code::calc_wh_with_min_width, edges::margin::Margin, text::FontRenderer};
|
||||
|
||||
use super::interface::{
|
||||
component::Component,
|
||||
style::{ComponentStyle, RawComponentStyle, Size},
|
||||
};
|
||||
|
||||
pub struct Breadcrumbs {
|
||||
children: Vec<Box<dyn Component>>,
|
||||
path: String,
|
||||
line_height: f32,
|
||||
has_breadcrumbs: bool,
|
||||
}
|
||||
|
||||
impl Component for Breadcrumbs {
|
||||
fn children(&self) -> &Vec<Box<dyn Component>> {
|
||||
&self.children
|
||||
}
|
||||
|
||||
fn style(&self) -> RawComponentStyle {
|
||||
let style = RawComponentStyle::default();
|
||||
|
||||
if self.has_breadcrumbs {
|
||||
let (w, h) = calc_wh_with_min_width(&self.path, 8., self.line_height);
|
||||
|
||||
style.size(Size::Num(w), Size::Num(h)).margin(Margin {
|
||||
top: 5.,
|
||||
..Margin::default()
|
||||
})
|
||||
} else {
|
||||
style
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_self(
|
||||
&self,
|
||||
pixmap: &mut tiny_skia::Pixmap,
|
||||
context: &super::interface::component::ComponentContext,
|
||||
render_params: &super::interface::component::RenderParams,
|
||||
style: &super::interface::style::ComponentStyle,
|
||||
_parent_style: &ComponentStyle,
|
||||
) -> super::interface::render_error::Result<()> {
|
||||
if self.has_breadcrumbs {
|
||||
let attrs = Attrs::new()
|
||||
.color(Color::rgb(128, 132, 139))
|
||||
.family(Family::Name(&context.take_snapshot_params.code_font_family));
|
||||
|
||||
FontRenderer::new(
|
||||
12.,
|
||||
self.line_height,
|
||||
context.scale_factor,
|
||||
&context.take_snapshot_params.fonts_folder,
|
||||
)
|
||||
.draw_text(
|
||||
render_params.x,
|
||||
render_params.y,
|
||||
style.width,
|
||||
self.line_height,
|
||||
vec![(&self.path, attrs)],
|
||||
pixmap,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Breadcrumbs {
|
||||
pub fn from_path(
|
||||
path: String,
|
||||
line_height: f32,
|
||||
separator: String,
|
||||
has_breadcrumbs: bool,
|
||||
) -> Breadcrumbs {
|
||||
let path = Regex::new("/")
|
||||
.unwrap()
|
||||
.replace_all(&path, separator)
|
||||
.to_string();
|
||||
|
||||
Breadcrumbs {
|
||||
children: vec![],
|
||||
path,
|
||||
line_height,
|
||||
has_breadcrumbs,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
use super::interface::component::Component;
|
||||
|
||||
pub struct CodeBlock {
|
||||
children: Vec<Box<dyn Component>>,
|
||||
}
|
||||
|
||||
impl Component for CodeBlock {
|
||||
fn children(&self) -> &Vec<Box<dyn Component>> {
|
||||
&self.children
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeBlock {
|
||||
pub fn from_children(children: Vec<Box<dyn Component>>) -> CodeBlock {
|
||||
CodeBlock { children }
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
use tiny_skia::Pixmap;
|
||||
|
||||
use super::interface::{
|
||||
component::{Component, ComponentContext, ComponentRenderParams},
|
||||
render_error::Result,
|
||||
style::Style,
|
||||
};
|
||||
|
||||
pub struct Container {
|
||||
children: Vec<Box<dyn Component>>,
|
||||
}
|
||||
|
||||
impl Component for Container {
|
||||
fn children(&self) -> &Vec<Box<dyn Component>> {
|
||||
&self.children
|
||||
}
|
||||
}
|
||||
|
||||
impl Container {
|
||||
pub fn from_children(children: Vec<Box<dyn Component>>) -> Container {
|
||||
Container { children }
|
||||
}
|
||||
|
||||
pub fn draw_root(&self, context: &ComponentContext) -> Result<Pixmap> {
|
||||
let style = self.parsed_style();
|
||||
let mut pixmap = Pixmap::new(
|
||||
(style.width * context.scale_factor) as u32,
|
||||
(style.height * context.scale_factor) as u32,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.draw(
|
||||
&mut pixmap,
|
||||
context,
|
||||
ComponentRenderParams::default(),
|
||||
Style::default(),
|
||||
Style::default(),
|
||||
)?;
|
||||
|
||||
Ok(pixmap)
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
pub mod code;
|
||||
pub mod mac_title_bar;
|
|
@ -1,75 +0,0 @@
|
|||
use crate::{
|
||||
code::{calc_wh_with_min_width, prepare_code, CHAR_WIDTH},
|
||||
components::interface::{
|
||||
component::{Component, ComponentContext, RenderParams},
|
||||
render_error,
|
||||
style::{ComponentStyle, RawComponentStyle, Size, Style},
|
||||
},
|
||||
highlight::Highlight,
|
||||
text::FontRenderer,
|
||||
};
|
||||
|
||||
pub struct Code {
|
||||
children: Vec<Box<dyn Component>>,
|
||||
line_height: f32,
|
||||
font_size: f32,
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl Component for Code {
|
||||
fn children(&self) -> &Vec<Box<dyn Component>> {
|
||||
&self.children
|
||||
}
|
||||
|
||||
fn style(&self) -> RawComponentStyle {
|
||||
let (w, h) = calc_wh_with_min_width(&self.value, CHAR_WIDTH, self.line_height);
|
||||
|
||||
Style::default().size(Size::Num(w), Size::Num(h))
|
||||
}
|
||||
|
||||
fn draw_self(
|
||||
&self,
|
||||
pixmap: &mut tiny_skia::Pixmap,
|
||||
context: &ComponentContext,
|
||||
render_params: &RenderParams,
|
||||
style: &ComponentStyle,
|
||||
_parent_style: &ComponentStyle,
|
||||
) -> render_error::Result<()> {
|
||||
let params = &context.take_snapshot_params;
|
||||
let highlight = Highlight::new(
|
||||
self.value.clone(),
|
||||
params.code_font_family.clone(),
|
||||
params.code_file_path.clone(),
|
||||
params.extension.clone(),
|
||||
);
|
||||
let highlight_result = highlight.parse(¶ms.themes_folder, ¶ms.theme)?;
|
||||
|
||||
FontRenderer::new(
|
||||
self.font_size,
|
||||
self.line_height,
|
||||
context.scale_factor,
|
||||
&context.take_snapshot_params.fonts_folder,
|
||||
)
|
||||
.draw_text(
|
||||
render_params.x,
|
||||
render_params.y,
|
||||
style.width,
|
||||
style.height,
|
||||
highlight_result.clone(),
|
||||
pixmap,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Code {
|
||||
pub fn new(value: String, line_height: f32, font_size: f32) -> Code {
|
||||
Code {
|
||||
value: prepare_code(&value),
|
||||
line_height,
|
||||
font_size,
|
||||
children: vec![],
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
use tiny_skia::{Color, FillRule, Paint, PathBuilder, Transform};
|
||||
|
||||
use crate::{
|
||||
components::interface::{
|
||||
component::{Component, ComponentContext, RenderParams},
|
||||
render_error,
|
||||
style::{ComponentStyle, RawComponentStyle, Size, Style},
|
||||
},
|
||||
edges::margin::Margin,
|
||||
};
|
||||
|
||||
pub struct MacTitleBar {
|
||||
radius: f32,
|
||||
children: Vec<Box<dyn Component>>,
|
||||
render_condition: bool,
|
||||
}
|
||||
|
||||
impl Component for MacTitleBar {
|
||||
fn children(&self) -> &Vec<Box<dyn Component>> {
|
||||
&self.children
|
||||
}
|
||||
|
||||
fn style(&self) -> RawComponentStyle {
|
||||
let demeter = self.radius * 2.;
|
||||
|
||||
Style::default()
|
||||
.size(Size::Num(demeter + 2. * 25.), Size::Num(demeter))
|
||||
.margin(Margin {
|
||||
bottom: 10.,
|
||||
..Margin::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn render_condition(&self) -> bool {
|
||||
return self.render_condition;
|
||||
}
|
||||
|
||||
fn draw_self(
|
||||
&self,
|
||||
pixmap: &mut tiny_skia::Pixmap,
|
||||
context: &ComponentContext,
|
||||
render_params: &RenderParams,
|
||||
_style: &ComponentStyle,
|
||||
_parent_style: &ComponentStyle,
|
||||
) -> render_error::Result<()> {
|
||||
self.draw_control_buttons(
|
||||
// Control bar construct by draw circles, after drawn, the path will be at the center,
|
||||
// so the x, y need to offset by radius of the circle, and the next shape will still
|
||||
// be drwan on the original point
|
||||
render_params.x + self.radius,
|
||||
render_params.y + self.radius,
|
||||
pixmap,
|
||||
vec![
|
||||
Color::from_rgba8(255, 94, 87, 255),
|
||||
Color::from_rgba8(255, 186, 46, 255),
|
||||
Color::from_rgba8(43, 200, 65, 255),
|
||||
],
|
||||
25.,
|
||||
Transform::from_scale(context.scale_factor, context.scale_factor),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MacTitleBar {
|
||||
pub fn from_radius(radius: f32, render_condition: bool) -> MacTitleBar {
|
||||
MacTitleBar {
|
||||
radius,
|
||||
children: vec![],
|
||||
render_condition,
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_control_buttons(
|
||||
&self,
|
||||
x: f32,
|
||||
y: f32,
|
||||
pixmap: &mut tiny_skia::Pixmap,
|
||||
colors: Vec<Color>,
|
||||
gap: f32,
|
||||
transform: Transform,
|
||||
) {
|
||||
for (index, color) in colors.into_iter().enumerate() {
|
||||
self.draw_control_button(x, y, pixmap, color, index as f32 * gap, transform);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_control_button(
|
||||
&self,
|
||||
x: f32,
|
||||
y: f32,
|
||||
pixmap: &mut tiny_skia::Pixmap,
|
||||
color: Color,
|
||||
x_offset: f32,
|
||||
transform: Transform,
|
||||
) {
|
||||
let mut path_builder = PathBuilder::new();
|
||||
|
||||
path_builder.push_circle(x + x_offset, y, self.radius);
|
||||
path_builder.close();
|
||||
|
||||
let path = path_builder.finish().unwrap();
|
||||
let mut paint = Paint::default();
|
||||
|
||||
paint.set_color(color);
|
||||
pixmap.fill_path(&path, &paint, FillRule::Winding, transform, None);
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
use super::{
|
||||
interface::{component::Component, style::ComponentStyle},
|
||||
rect::EDITOR_PADDING,
|
||||
};
|
||||
use tiny_skia::{Color, Paint, Rect, Transform};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HighlightCodeBlock {
|
||||
children: Vec<Box<dyn Component>>,
|
||||
line_height: f32,
|
||||
start_line_number: usize,
|
||||
end_line_number: usize,
|
||||
render_condition: bool,
|
||||
}
|
||||
|
||||
impl Component for HighlightCodeBlock {
|
||||
fn children(&self) -> &Vec<Box<dyn Component>> {
|
||||
&self.children
|
||||
}
|
||||
|
||||
fn render_condition(&self) -> bool {
|
||||
self.render_condition
|
||||
}
|
||||
|
||||
fn draw_self(
|
||||
&self,
|
||||
pixmap: &mut tiny_skia::Pixmap,
|
||||
context: &super::interface::component::ComponentContext,
|
||||
render_params: &super::interface::component::RenderParams,
|
||||
_style: &super::interface::style::ComponentStyle,
|
||||
parent_style: &ComponentStyle,
|
||||
) -> super::interface::render_error::Result<()> {
|
||||
let mut paint = Paint::default();
|
||||
let start_y_offset = (self.start_line_number - 1) as f32 * self.line_height;
|
||||
|
||||
paint.anti_alias = false;
|
||||
paint.set_color(Color::from_rgba8(255, 255, 255, 10));
|
||||
pixmap.fill_rect(
|
||||
Rect::from_xywh(
|
||||
render_params.x - EDITOR_PADDING,
|
||||
render_params.y + start_y_offset,
|
||||
parent_style.width + EDITOR_PADDING * 2.,
|
||||
(self.end_line_number - self.start_line_number + 1) as f32 * self.line_height,
|
||||
)
|
||||
.unwrap(),
|
||||
&paint,
|
||||
Transform::from_scale(context.scale_factor, context.scale_factor),
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HighlightCodeBlock {
|
||||
pub fn from_line_number(
|
||||
start_line_number: Option<usize>,
|
||||
end_line_number: Option<usize>,
|
||||
line_height: f32,
|
||||
) -> HighlightCodeBlock {
|
||||
if end_line_number < start_line_number {
|
||||
panic!("end_line_number should be greater than start_line_number")
|
||||
}
|
||||
|
||||
match start_line_number {
|
||||
Some(start_line_number) => HighlightCodeBlock {
|
||||
render_condition: true,
|
||||
children: vec![],
|
||||
line_height,
|
||||
start_line_number,
|
||||
end_line_number: end_line_number.unwrap(),
|
||||
},
|
||||
None => HighlightCodeBlock::default(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
pub mod component;
|
||||
pub mod render_error;
|
||||
pub mod style;
|
|
@ -1,204 +0,0 @@
|
|||
use super::{
|
||||
render_error,
|
||||
style::{ComponentAlign, ComponentStyle, RawComponentStyle, Size, Style},
|
||||
};
|
||||
use crate::{config::TakeSnapshotParams, edges::edge::Edge};
|
||||
use std::sync::Arc;
|
||||
use tiny_skia::Pixmap;
|
||||
|
||||
pub struct ComponentContext {
|
||||
pub scale_factor: f32,
|
||||
pub take_snapshot_params: Arc<TakeSnapshotParams>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct RenderParams {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ComponentRenderParams {
|
||||
pub parent_render_params: RenderParams,
|
||||
pub sibling_render_params: RenderParams,
|
||||
}
|
||||
|
||||
impl ComponentRenderParams {
|
||||
fn parse_into_render_params_with_style(
|
||||
&self,
|
||||
parent_style: ComponentStyle,
|
||||
sibling_style: ComponentStyle,
|
||||
style: ComponentStyle,
|
||||
) -> RenderParams {
|
||||
match parent_style.align {
|
||||
ComponentAlign::Row => RenderParams {
|
||||
x: self.sibling_render_params.x
|
||||
+ sibling_style.width
|
||||
+ style.margin.left
|
||||
+ sibling_style.padding.horizontal(),
|
||||
y: self.sibling_render_params.y + style.margin.top,
|
||||
},
|
||||
ComponentAlign::Column => RenderParams {
|
||||
x: self.sibling_render_params.x + style.margin.left,
|
||||
y: self.sibling_render_params.y
|
||||
+ style.margin.top
|
||||
+ sibling_style.height
|
||||
+ sibling_style.padding.vertical(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Component {
|
||||
fn children(&self) -> &Vec<Box<dyn Component>>;
|
||||
|
||||
fn align(&self) -> ComponentAlign {
|
||||
ComponentAlign::Row
|
||||
}
|
||||
|
||||
fn initialize(&self, render_params: &RenderParams) -> RenderParams {
|
||||
render_params.clone()
|
||||
}
|
||||
|
||||
// The render_condition determines whether the component should be rendered or not
|
||||
fn render_condition(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
// The difference with render_condition is that self_render_condition still renders childrens
|
||||
fn self_render_condition(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn draw_self(
|
||||
&self,
|
||||
_pixmap: &mut Pixmap,
|
||||
_context: &ComponentContext,
|
||||
_render_params: &RenderParams,
|
||||
_style: &ComponentStyle,
|
||||
_parent_style: &ComponentStyle,
|
||||
) -> render_error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn style(&self) -> RawComponentStyle {
|
||||
RawComponentStyle::default()
|
||||
}
|
||||
|
||||
fn parse_size(&self, size: Size, dynamic_value: f32) -> f32 {
|
||||
match size {
|
||||
Size::Num(num) => num,
|
||||
Size::Dynamic => dynamic_value,
|
||||
}
|
||||
}
|
||||
|
||||
fn parsed_style(&self) -> Style<f32> {
|
||||
// If render_condition return false, the whole component shouldn't rendered,
|
||||
// includes its children
|
||||
if !self.render_condition() {
|
||||
return ComponentStyle::default();
|
||||
}
|
||||
|
||||
// If self_render_condition return false, the component shouldn't rendered,
|
||||
// so the corresponding style should be cleared
|
||||
let style = if self.self_render_condition() {
|
||||
self.style()
|
||||
} else {
|
||||
RawComponentStyle::default()
|
||||
};
|
||||
let (width, height) = self.get_dynamic_wh();
|
||||
let width = self.parse_size(style.width, width)
|
||||
+ style.padding.horizontal()
|
||||
+ style.margin.horizontal();
|
||||
|
||||
Style {
|
||||
min_width: style.min_width,
|
||||
width: if width > style.min_width {
|
||||
width
|
||||
} else {
|
||||
style.min_width
|
||||
},
|
||||
height: self.parse_size(style.height, height)
|
||||
+ style.padding.vertical()
|
||||
+ style.margin.vertical(),
|
||||
align: style.align,
|
||||
padding: style.padding,
|
||||
margin: style.margin,
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
pixmap: &mut Pixmap,
|
||||
context: &ComponentContext,
|
||||
component_render_params: ComponentRenderParams,
|
||||
parent_style: ComponentStyle,
|
||||
sibling_style: ComponentStyle,
|
||||
) -> render_error::Result<RenderParams> {
|
||||
let style = self.parsed_style();
|
||||
let render_params = self.initialize(
|
||||
&component_render_params.parse_into_render_params_with_style(
|
||||
parent_style.clone(),
|
||||
sibling_style.clone(),
|
||||
style.clone(),
|
||||
),
|
||||
);
|
||||
|
||||
// Render nothing on paint if render_condition return false
|
||||
if !self.render_condition() {
|
||||
return Ok(render_params.clone());
|
||||
}
|
||||
|
||||
if self.self_render_condition() {
|
||||
self.draw_self(pixmap, context, &render_params, &style, &parent_style)?;
|
||||
}
|
||||
|
||||
let children = self.children();
|
||||
let mut sibling_render_params = RenderParams {
|
||||
x: render_params.x + style.padding.left,
|
||||
y: render_params.y + style.padding.top,
|
||||
};
|
||||
let mut sibling_style = ComponentStyle::default();
|
||||
|
||||
for child in children {
|
||||
sibling_render_params = child.draw(
|
||||
pixmap,
|
||||
context,
|
||||
ComponentRenderParams {
|
||||
parent_render_params: render_params.clone(),
|
||||
sibling_render_params,
|
||||
},
|
||||
style.clone(),
|
||||
sibling_style,
|
||||
)?;
|
||||
sibling_style = child.parsed_style();
|
||||
}
|
||||
|
||||
Ok(render_params.clone())
|
||||
}
|
||||
|
||||
// Dynamic calculate width and height of children, if the children is empty, get_dynamic_wh
|
||||
// will return (0., 0.)
|
||||
fn get_dynamic_wh(&self) -> (f32, f32) {
|
||||
let children = self.children();
|
||||
let calc_children_wh = |cb: fn((f32, f32), &Box<dyn Component>) -> (f32, f32)| {
|
||||
children.iter().fold((0., 0.), cb)
|
||||
};
|
||||
let style = self.style();
|
||||
|
||||
match style.align {
|
||||
// If align is row, width is sum of children width, height is max of children height
|
||||
ComponentAlign::Row => calc_children_wh(|(w, h), child| {
|
||||
let style = child.parsed_style();
|
||||
|
||||
(w + style.width, h.max(style.height))
|
||||
}),
|
||||
// If align is column, width is max of children width, height is sum of children height
|
||||
ComponentAlign::Column => calc_children_wh(|(w, h), child| {
|
||||
let style = child.parsed_style();
|
||||
|
||||
(w.max(style.width), h + style.height)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, RenderError>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RenderError {
|
||||
#[error("Highlight code failed!")]
|
||||
HighlightThemeLoadFailed,
|
||||
|
||||
#[error("No such highlight syntax for {0}")]
|
||||
HighlightCodeFailed(String),
|
||||
|
||||
#[error("Unable to parse unknown background theme {0}")]
|
||||
UnknownBackgroundTheme(String),
|
||||
|
||||
#[error("Invalid hex color {0}")]
|
||||
InvalidHexColor(String),
|
||||
|
||||
#[error("No such file {0}")]
|
||||
NoSuchFile(String),
|
||||
}
|
||||
|
||||
impl From<RenderError> for nvim_oxi::api::Error {
|
||||
fn from(err: RenderError) -> Self {
|
||||
nvim_oxi::api::Error::Other(err.to_string())
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
use crate::edges::{margin::Margin, padding::Padding};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ComponentAlign {
|
||||
Row,
|
||||
Column,
|
||||
}
|
||||
|
||||
pub enum Size {
|
||||
Dynamic,
|
||||
Num(f32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Style<T> {
|
||||
pub width: T,
|
||||
pub height: T,
|
||||
pub min_width: f32,
|
||||
pub align: ComponentAlign,
|
||||
pub padding: Padding,
|
||||
pub margin: Margin,
|
||||
}
|
||||
|
||||
pub type RawComponentStyle = Style<Size>;
|
||||
pub type ComponentStyle = Style<f32>;
|
||||
|
||||
impl Default for RawComponentStyle {
|
||||
fn default() -> Self {
|
||||
Style {
|
||||
min_width: 0.,
|
||||
width: Size::Dynamic,
|
||||
height: Size::Dynamic,
|
||||
align: ComponentAlign::Row,
|
||||
padding: Padding::default(),
|
||||
margin: Margin::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ComponentStyle {
|
||||
fn default() -> Self {
|
||||
Style {
|
||||
min_width: 0.,
|
||||
width: 0.,
|
||||
height: 0.,
|
||||
align: ComponentAlign::Row,
|
||||
padding: Padding::default(),
|
||||
margin: Margin::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RawComponentStyle {
|
||||
pub fn size(mut self, width: Size, height: Size) -> Self {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
// Only works if the width is calculate dynamically
|
||||
pub fn min_width(mut self, min_width: f32) -> Self {
|
||||
self.min_width = min_width;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn align(mut self, align: ComponentAlign) -> Self {
|
||||
self.align = align;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn padding(mut self, padding: Padding) -> Self {
|
||||
self.padding = padding;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn margin(mut self, margin: Margin) -> Self {
|
||||
self.margin = margin;
|
||||
self
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
use super::interface::{
|
||||
component::{Component, ComponentContext, RenderParams},
|
||||
render_error,
|
||||
style::{ComponentStyle, RawComponentStyle, Size, Style},
|
||||
};
|
||||
use crate::{code::CHAR_WIDTH, edges::margin::Margin, text::FontRenderer};
|
||||
use cosmic_text::{Attrs, Color, Family};
|
||||
|
||||
const FONT_SIZE: f32 = 14.;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LineNumber {
|
||||
children: Vec<Box<dyn Component>>,
|
||||
line_height: f32,
|
||||
render_condition: bool,
|
||||
line_number_content: Vec<String>,
|
||||
number_of_digit: usize,
|
||||
}
|
||||
|
||||
impl Component for LineNumber {
|
||||
fn render_condition(&self) -> bool {
|
||||
return self.render_condition;
|
||||
}
|
||||
|
||||
fn children(&self) -> &Vec<Box<dyn Component>> {
|
||||
&self.children
|
||||
}
|
||||
|
||||
fn style(&self) -> RawComponentStyle {
|
||||
Style::default()
|
||||
.size(
|
||||
Size::Num(CHAR_WIDTH * self.number_of_digit as f32),
|
||||
Size::Num(self.line_number_content.len() as f32 * self.line_height),
|
||||
)
|
||||
.margin(Margin {
|
||||
right: 10.,
|
||||
..Margin::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn draw_self(
|
||||
&self,
|
||||
pixmap: &mut tiny_skia::Pixmap,
|
||||
context: &ComponentContext,
|
||||
render_params: &RenderParams,
|
||||
style: &ComponentStyle,
|
||||
_parent_style: &ComponentStyle,
|
||||
) -> render_error::Result<()> {
|
||||
FontRenderer::new(
|
||||
FONT_SIZE,
|
||||
self.line_height,
|
||||
context.scale_factor,
|
||||
&context.take_snapshot_params.fonts_folder,
|
||||
)
|
||||
.draw_text(
|
||||
render_params.x,
|
||||
render_params.y,
|
||||
style.width,
|
||||
style.height,
|
||||
vec![(
|
||||
&self.line_number_content.join("\n"),
|
||||
Attrs::new()
|
||||
.color(Color::rgb(73, 81, 98))
|
||||
.family(Family::Name(&context.take_snapshot_params.code_font_family)),
|
||||
)],
|
||||
pixmap,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl LineNumber {
|
||||
pub fn new(content: &str, start_line_number: Option<usize>, line_height: f32) -> LineNumber {
|
||||
match start_line_number {
|
||||
None => LineNumber::default(),
|
||||
Some(start_line_number) => {
|
||||
let lines = content.split("\n").collect::<Vec<&str>>();
|
||||
let max_line_number = lines.len() + start_line_number;
|
||||
let number_of_digit = (max_line_number - 1).to_string().len();
|
||||
|
||||
LineNumber {
|
||||
line_number_content: (start_line_number..max_line_number)
|
||||
.map(|line_number| {
|
||||
format!(
|
||||
"{:>width$}",
|
||||
line_number.to_string(),
|
||||
width = number_of_digit
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>(),
|
||||
number_of_digit,
|
||||
children: vec![],
|
||||
render_condition: true,
|
||||
line_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
use super::interface::{
|
||||
component::{Component, ComponentContext, RenderParams},
|
||||
render_error,
|
||||
style::{ComponentAlign, ComponentStyle, RawComponentStyle, Style},
|
||||
};
|
||||
use crate::edges::padding::Padding;
|
||||
use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Transform};
|
||||
|
||||
pub const EDITOR_PADDING: f32 = 20.;
|
||||
|
||||
pub struct Rect {
|
||||
radius: f32,
|
||||
min_width: f32,
|
||||
children: Vec<Box<dyn Component>>,
|
||||
}
|
||||
|
||||
impl Component for Rect {
|
||||
fn children(&self) -> &Vec<Box<dyn Component>> {
|
||||
&self.children
|
||||
}
|
||||
|
||||
fn style(&self) -> RawComponentStyle {
|
||||
Style::default()
|
||||
.min_width(self.min_width)
|
||||
.align(ComponentAlign::Column)
|
||||
.padding(Padding::from_value(EDITOR_PADDING))
|
||||
}
|
||||
|
||||
fn draw_self(
|
||||
&self,
|
||||
pixmap: &mut Pixmap,
|
||||
context: &ComponentContext,
|
||||
render_params: &RenderParams,
|
||||
style: &ComponentStyle,
|
||||
_parent_style: &ComponentStyle,
|
||||
) -> render_error::Result<()> {
|
||||
let mut path_builder = PathBuilder::new();
|
||||
let x = render_params.x;
|
||||
let y = render_params.y;
|
||||
let w = style.width;
|
||||
let h = style.height;
|
||||
|
||||
let rect_width = w - 2. * self.radius;
|
||||
let rect_height = h - 2. * self.radius;
|
||||
|
||||
path_builder.move_to(x + self.radius, y);
|
||||
path_builder.line_to(x + self.radius + rect_width, y);
|
||||
path_builder.line_to(x + self.radius + rect_width, y + self.radius);
|
||||
|
||||
path_builder.line_to(x + rect_width + self.radius * 2., y + self.radius);
|
||||
|
||||
path_builder.line_to(
|
||||
x + rect_width + self.radius * 2.,
|
||||
y + rect_height + self.radius,
|
||||
);
|
||||
path_builder.line_to(x + rect_width + self.radius, y + rect_height + self.radius);
|
||||
path_builder.line_to(
|
||||
x + rect_width + self.radius,
|
||||
y + rect_height + self.radius * 2.,
|
||||
);
|
||||
|
||||
path_builder.line_to(x + self.radius, y + rect_height + self.radius * 2.);
|
||||
path_builder.line_to(x + self.radius, y + rect_height + self.radius);
|
||||
|
||||
path_builder.line_to(x, y + rect_height + self.radius);
|
||||
|
||||
path_builder.line_to(x, y + self.radius);
|
||||
path_builder.line_to(x + self.radius, y + self.radius);
|
||||
path_builder.line_to(x + self.radius, y);
|
||||
path_builder.line_to(x + self.radius + rect_width, y);
|
||||
path_builder.push_circle(
|
||||
x + rect_width + self.radius,
|
||||
y + rect_height + self.radius,
|
||||
self.radius,
|
||||
);
|
||||
path_builder.push_circle(x + self.radius + rect_width, y + self.radius, self.radius);
|
||||
path_builder.push_circle(x + self.radius, y + self.radius, self.radius);
|
||||
path_builder.push_circle(x + self.radius, y + rect_height + self.radius, self.radius);
|
||||
|
||||
path_builder.close();
|
||||
let path = path_builder.finish().unwrap();
|
||||
let mut paint = Paint::default();
|
||||
paint.set_color_rgba8(40, 44, 52, 237);
|
||||
|
||||
pixmap.fill_path(
|
||||
&path,
|
||||
&paint,
|
||||
FillRule::Winding,
|
||||
Transform::from_scale(context.scale_factor, context.scale_factor),
|
||||
// Transform::identity(),
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
pub fn new(radius: f32, min_width: Option<f32>, children: Vec<Box<dyn Component>>) -> Rect {
|
||||
Rect {
|
||||
radius,
|
||||
children,
|
||||
min_width: min_width.unwrap_or(0.),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
use cosmic_text::{Align, Attrs, Family};
|
||||
use tiny_skia::Pixmap;
|
||||
|
||||
use crate::{edges::margin::Margin, text::FontRenderer};
|
||||
|
||||
use super::interface::{
|
||||
component::{Component, ComponentContext, RenderParams},
|
||||
render_error,
|
||||
style::{ComponentStyle, RawComponentStyle},
|
||||
};
|
||||
|
||||
pub struct Watermark {
|
||||
children: Vec<Box<dyn Component>>,
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl Component for Watermark {
|
||||
fn draw_self(
|
||||
&self,
|
||||
pixmap: &mut Pixmap,
|
||||
context: &ComponentContext,
|
||||
render_params: &RenderParams,
|
||||
_style: &ComponentStyle,
|
||||
_parent_style: &ComponentStyle,
|
||||
) -> render_error::Result<()> {
|
||||
let params = &context.take_snapshot_params;
|
||||
|
||||
let attrs = Attrs::new().family(Family::Name(
|
||||
&context.take_snapshot_params.watermark_font_family,
|
||||
));
|
||||
|
||||
FontRenderer::new(
|
||||
20.,
|
||||
20.,
|
||||
context.scale_factor,
|
||||
&context.take_snapshot_params.fonts_folder,
|
||||
)
|
||||
.draw_line(
|
||||
0.,
|
||||
render_params.y,
|
||||
pixmap.width() as f32,
|
||||
pixmap.height() as f32,
|
||||
¶ms.watermark,
|
||||
attrs,
|
||||
Some(Align::Center),
|
||||
pixmap,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn children(&self) -> &Vec<Box<dyn Component>> {
|
||||
&self.children
|
||||
}
|
||||
|
||||
fn render_condition(&self) -> bool {
|
||||
self.value != ""
|
||||
}
|
||||
|
||||
fn style(&self) -> RawComponentStyle {
|
||||
let default_style = RawComponentStyle::default();
|
||||
|
||||
if self.value != "" {
|
||||
default_style.margin(Margin {
|
||||
bottom: 22.,
|
||||
top: 15.,
|
||||
..Margin::default()
|
||||
})
|
||||
} else {
|
||||
default_style
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Watermark {
|
||||
pub fn new(value: String) -> Watermark {
|
||||
Watermark {
|
||||
children: vec![],
|
||||
value,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
use nvim_oxi::conversion::{Error as ConversionError, FromObject, ToObject};
|
||||
use nvim_oxi::serde::{Deserializer, Serializer};
|
||||
use nvim_oxi::{lua, Object};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct TakeSnapshotParams {
|
||||
// Whether to display the MacOS style title bar
|
||||
pub mac_window_bar: bool,
|
||||
// Wartermark of the code snapshot
|
||||
pub watermark: String,
|
||||
// Editor title
|
||||
pub title: Option<String>,
|
||||
pub code_font_family: String,
|
||||
pub watermark_font_family: String,
|
||||
pub code: String,
|
||||
pub code_file_path: String,
|
||||
pub extension: Option<String>,
|
||||
pub save_path: Option<String>,
|
||||
pub themes_folder: String,
|
||||
pub fonts_folder: String,
|
||||
pub theme: String,
|
||||
pub bg_theme: String,
|
||||
pub bg_color: Option<String>,
|
||||
// Breadcrumbs path
|
||||
pub file_path: String,
|
||||
pub breadcrumbs_separator: String,
|
||||
pub has_breadcrumbs: bool,
|
||||
pub start_line_number: Option<usize>,
|
||||
pub highlight_start_line_number: Option<usize>,
|
||||
pub highlight_end_line_number: Option<usize>,
|
||||
pub min_width: Option<f32>,
|
||||
pub bg_x_padding: f32,
|
||||
pub bg_y_padding: f32,
|
||||
pub bg_padding: Option<f32>,
|
||||
}
|
||||
|
||||
impl FromObject for TakeSnapshotParams {
|
||||
fn from_object(obj: Object) -> Result<Self, ConversionError> {
|
||||
Self::deserialize(Deserializer::new(obj)).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToObject for TakeSnapshotParams {
|
||||
fn to_object(self) -> Result<Object, ConversionError> {
|
||||
self.serialize(Serializer::new()).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl lua::Poppable for TakeSnapshotParams {
|
||||
unsafe fn pop(lstate: *mut lua::ffi::lua_State) -> Result<Self, lua::Error> {
|
||||
let obj = Object::pop(lstate)?;
|
||||
Self::from_object(obj).map_err(lua::Error::pop_error_from_err::<Self, _>)
|
||||
}
|
||||
}
|
||||
|
||||
impl lua::Pushable for TakeSnapshotParams {
|
||||
unsafe fn push(self, lstate: *mut lua::ffi::lua_State) -> Result<std::ffi::c_int, lua::Error> {
|
||||
self.to_object()
|
||||
.map_err(lua::Error::push_error_from_err::<Self, _>)?
|
||||
.push(lstate)
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
use crate::{config::TakeSnapshotParams, snapshot::take_snapshot};
|
||||
#[cfg(target_os = "linux")]
|
||||
use arboard::SetExtLinux;
|
||||
use arboard::{Clipboard, ImageData};
|
||||
|
||||
use nvim_oxi::api;
|
||||
|
||||
// The function will be called as FFI on Lua side
|
||||
#[allow(dead_code)]
|
||||
pub fn copy_into_clipboard(config: TakeSnapshotParams) -> Result<(), api::Error> {
|
||||
let pixmap = take_snapshot(config.clone())?;
|
||||
let premultplied_colors = pixmap.pixels();
|
||||
let colors = premultplied_colors
|
||||
.iter()
|
||||
.map(|premultplied_color| {
|
||||
vec![
|
||||
premultplied_color.red(),
|
||||
premultplied_color.green(),
|
||||
premultplied_color.blue(),
|
||||
premultplied_color.alpha(),
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
let img_data = ImageData {
|
||||
width: pixmap.width() as usize,
|
||||
height: pixmap.height() as usize,
|
||||
bytes: colors.into(),
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
std::thread::spawn(move || {
|
||||
Clipboard::new()
|
||||
.unwrap()
|
||||
.set()
|
||||
.wait()
|
||||
.image(img_data)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
Clipboard::new().unwrap().set_image(img_data).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
use crate::{
|
||||
code::{calc_max_line_number_length, calc_wh, prepare_code},
|
||||
config::TakeSnapshotParams,
|
||||
};
|
||||
use arboard::Clipboard;
|
||||
#[cfg(target_os = "linux")]
|
||||
use arboard::SetExtLinux;
|
||||
use nvim_oxi::api;
|
||||
use std::cmp::max;
|
||||
|
||||
const SPACE_BOTH_SIDE: usize = 2;
|
||||
|
||||
fn optional(component: String, is_view: bool) -> String {
|
||||
if is_view {
|
||||
component
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn copy_ascii(params: TakeSnapshotParams) -> Result<(), api::Error> {
|
||||
let code = prepare_code(¶ms.code);
|
||||
let (width, height) = calc_wh(&code, 1., 1.);
|
||||
let calc_line_number_width =
|
||||
|start_line_number: usize| calc_max_line_number_length(height as usize, start_line_number);
|
||||
let frame_width = max(width as usize, params.file_path.len()) + SPACE_BOTH_SIDE;
|
||||
let frame_width = match params.start_line_number {
|
||||
Some(start_line_number) => {
|
||||
frame_width + SPACE_BOTH_SIDE + calc_line_number_width(start_line_number)
|
||||
}
|
||||
None => frame_width,
|
||||
};
|
||||
let line = format!("│{}│\n", "─".repeat(frame_width));
|
||||
let frame_width_with_content = frame_width - 1;
|
||||
let top_frame = format!("╭{}╮\n", "─".repeat(frame_width));
|
||||
let bottom_frame = format!("╰{}╯", "─".repeat(frame_width));
|
||||
let code = code
|
||||
.lines()
|
||||
.enumerate()
|
||||
.map(|(i, line)| {
|
||||
format!(
|
||||
"│ {:1$} │\n",
|
||||
match params.start_line_number {
|
||||
Some(start_line_number) => format!(
|
||||
"{:1$} {line}",
|
||||
start_line_number + i,
|
||||
calc_line_number_width(start_line_number),
|
||||
),
|
||||
None => line.to_string(),
|
||||
},
|
||||
frame_width_with_content - 1
|
||||
)
|
||||
})
|
||||
.collect::<String>();
|
||||
let text_line = |text: &str| format!("│ {:1$}│\n", text, frame_width_with_content);
|
||||
let breadcrumbs = optional(
|
||||
format!("{}{line}", text_line(¶ms.file_path)),
|
||||
params.has_breadcrumbs,
|
||||
);
|
||||
let ascii_snapshot = format!("{top_frame}{breadcrumbs}{code}{bottom_frame}");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
std::thread::spawn(move || {
|
||||
Clipboard::new()
|
||||
.unwrap()
|
||||
.set()
|
||||
.wait()
|
||||
.text(ascii_snapshot)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
Clipboard::new()
|
||||
.unwrap()
|
||||
.set_text(ascii_snapshot)
|
||||
.map_err(|err| api::Error::Other(err.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
pub mod edge;
|
||||
pub mod margin;
|
||||
pub mod padding;
|
|
@ -1,5 +0,0 @@
|
|||
pub trait Edge {
|
||||
fn horizontal(&self) -> f32;
|
||||
|
||||
fn vertical(&self) -> f32;
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
use super::edge::Edge;
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Margin {
|
||||
pub left: f32,
|
||||
pub right: f32,
|
||||
pub top: f32,
|
||||
pub bottom: f32,
|
||||
}
|
||||
|
||||
impl Edge for Margin {
|
||||
fn horizontal(&self) -> f32 {
|
||||
self.left + self.right
|
||||
}
|
||||
|
||||
fn vertical(&self) -> f32 {
|
||||
self.bottom + self.top
|
||||
}
|
||||
}
|
||||
|
||||
impl Margin {
|
||||
#[allow(dead_code)]
|
||||
pub fn from_value(value: f32) -> Margin {
|
||||
Margin {
|
||||
left: value,
|
||||
right: value,
|
||||
top: value,
|
||||
bottom: value,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
use super::edge::Edge;
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Padding {
|
||||
pub left: f32,
|
||||
pub right: f32,
|
||||
pub top: f32,
|
||||
pub bottom: f32,
|
||||
}
|
||||
|
||||
impl Edge for Padding {
|
||||
fn horizontal(&self) -> f32 {
|
||||
self.left + self.right
|
||||
}
|
||||
|
||||
fn vertical(&self) -> f32 {
|
||||
self.bottom + self.top
|
||||
}
|
||||
}
|
||||
|
||||
impl Padding {
|
||||
pub fn from_value(value: f32) -> Padding {
|
||||
Padding {
|
||||
left: value,
|
||||
right: value,
|
||||
top: value,
|
||||
bottom: value,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use cosmic_text::{Attrs, Family, Style, Weight};
|
||||
use syntect::{
|
||||
easy::HighlightLines,
|
||||
highlighting::{FontStyle, ThemeSet},
|
||||
parsing::{SyntaxReference, SyntaxSet},
|
||||
util::LinesWithEndings,
|
||||
};
|
||||
|
||||
use crate::components::interface::render_error::RenderError;
|
||||
|
||||
type SourceMap = HashMap<&'static str, &'static str>;
|
||||
|
||||
pub struct Highlight {
|
||||
content: String,
|
||||
code_file_path: String,
|
||||
extension: Option<String>,
|
||||
font_family: String,
|
||||
highlighting_language_source_map: SourceMap,
|
||||
}
|
||||
|
||||
pub type HighlightResult<'a> = Vec<(&'a str, Attrs<'a>)>;
|
||||
|
||||
impl Highlight {
|
||||
pub fn new(
|
||||
content: String,
|
||||
font_family: String,
|
||||
code_file_path: String,
|
||||
extension: Option<String>,
|
||||
) -> Highlight {
|
||||
Highlight {
|
||||
content,
|
||||
code_file_path,
|
||||
extension,
|
||||
font_family,
|
||||
highlighting_language_source_map: HashMap::from([("PHP", "<?php")]),
|
||||
}
|
||||
}
|
||||
|
||||
fn guess_syntax(&self, syntax_set: &SyntaxSet) -> Result<SyntaxReference, RenderError> {
|
||||
// The extension exist only when users specified explicitly
|
||||
// By default, using filepath to detect what syntax should use
|
||||
let syntax = match &self.extension {
|
||||
Some(extension) => syntax_set
|
||||
.find_syntax_by_extension(&extension)
|
||||
.ok_or(RenderError::HighlightCodeFailed(extension.to_string()))?,
|
||||
None => syntax_set
|
||||
.find_syntax_for_file(&self.code_file_path)
|
||||
.map_err(|_| RenderError::NoSuchFile(self.code_file_path.to_string()))?
|
||||
.ok_or(RenderError::HighlightCodeFailed(
|
||||
self.code_file_path.to_string(),
|
||||
))?,
|
||||
};
|
||||
|
||||
// The Syntect clearly distinguish between PHP and PHP Source
|
||||
// Should use PHP as highlight language if the source content contains "<php" tag
|
||||
// Should use PHP Source as highlight language if the source content not contains "<php" tag
|
||||
if let Some(identifier) = self.highlighting_language_source_map.get(&syntax.name[..]) {
|
||||
if !self.content.contains(identifier) {
|
||||
return Ok(syntax_set
|
||||
.find_syntax_by_name(&format!("{} Source", &syntax.name))
|
||||
.unwrap_or(syntax)
|
||||
.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(syntax.to_owned())
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
&self,
|
||||
theme_folder: &str,
|
||||
theme: &str,
|
||||
) -> Result<Vec<(&str, Attrs)>, RenderError> {
|
||||
let syntax_set = two_face::syntax::extra_newlines();
|
||||
let theme_set = ThemeSet::load_from_folder(theme_folder)
|
||||
.map_err(|_| RenderError::HighlightThemeLoadFailed)?;
|
||||
let syntax = &self.guess_syntax(&syntax_set)?;
|
||||
let mut highlight = HighlightLines::new(syntax, &theme_set.themes[theme]);
|
||||
let attrs = Attrs::new().family(Family::Name(self.font_family.as_ref()));
|
||||
|
||||
// Highlight the content line by line using highlight_line function
|
||||
Ok(LinesWithEndings::from(&self.content)
|
||||
.map(|line| {
|
||||
highlight
|
||||
.highlight_line(line, &syntax_set)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|(style, str)| {
|
||||
let syntect::highlighting::Color { r, g, b, a: _ } = style.foreground;
|
||||
let attrs = match style.font_style {
|
||||
FontStyle::BOLD => attrs.weight(Weight::BOLD),
|
||||
FontStyle::ITALIC => attrs.style(Style::Italic),
|
||||
FontStyle::UNDERLINE => attrs.style(Style::Normal),
|
||||
_ => attrs,
|
||||
};
|
||||
|
||||
(str, attrs.color(cosmic_text::Color::rgb(r, g, b)))
|
||||
})
|
||||
.collect::<HighlightResult>()
|
||||
})
|
||||
.fold(vec![], |acc, cur| [acc, cur].concat())
|
||||
.into_iter()
|
||||
.collect::<HighlightResult>())
|
||||
}
|
||||
}
|
|
@ -1,30 +1,62 @@
|
|||
mod code;
|
||||
mod color;
|
||||
mod components;
|
||||
mod config;
|
||||
mod copy;
|
||||
mod copy_ascii;
|
||||
mod edges;
|
||||
mod highlight;
|
||||
mod path;
|
||||
mod save;
|
||||
mod snapshot;
|
||||
mod text;
|
||||
mod snapshot_config;
|
||||
|
||||
use config::TakeSnapshotParams;
|
||||
use copy::copy_into_clipboard;
|
||||
use copy_ascii::copy_ascii;
|
||||
use nvim_oxi::{api, Dictionary, Function};
|
||||
use save::save_snapshot;
|
||||
use codesnap::snapshot::{image_snapshot::ImageSnapshot, snapshot_data::SnapshotData};
|
||||
use mlua::prelude::*;
|
||||
use snapshot_config::SnapshotConfigLua;
|
||||
|
||||
#[nvim_oxi::plugin]
|
||||
fn generator() -> nvim_oxi::Result<Dictionary> {
|
||||
let copy_into_clipboard: Function<TakeSnapshotParams, Result<(), api::Error>> =
|
||||
Function::from_fn(copy_into_clipboard);
|
||||
|
||||
Ok(Dictionary::from_iter([
|
||||
("copy_into_clipboard", copy_into_clipboard),
|
||||
("save_snapshot", Function::from_fn(save_snapshot)),
|
||||
("copy_ascii", Function::from_fn(copy_ascii)),
|
||||
]))
|
||||
enum SnapshotType {
|
||||
Png,
|
||||
Svg,
|
||||
Html,
|
||||
}
|
||||
|
||||
impl From<String> for SnapshotType {
|
||||
fn from(value: String) -> Self {
|
||||
match value.as_str() {
|
||||
"png" => SnapshotType::Png,
|
||||
"svg" => SnapshotType::Svg,
|
||||
"html" => SnapshotType::Html,
|
||||
_ => SnapshotType::Png,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SnapshotType {
|
||||
fn snapshot_data(&self, image_snapshot: ImageSnapshot) -> LuaResult<SnapshotData> {
|
||||
let data = match self {
|
||||
SnapshotType::Png => image_snapshot.png_data(),
|
||||
SnapshotType::Svg => todo!(),
|
||||
SnapshotType::Html => todo!(),
|
||||
}
|
||||
.map_err(|_| mlua::Error::RuntimeError("Failed to generate snapshot data".to_string()))?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
fn save(
|
||||
_: &Lua,
|
||||
(snapshot_type, path, config): (String, String, SnapshotConfigLua),
|
||||
) -> LuaResult<()> {
|
||||
let image_snapshot = config
|
||||
.0
|
||||
.create_snapshot()
|
||||
.map_err(|_| mlua::Error::RuntimeError("Failed to create snapshot".to_string()))?;
|
||||
let snapshot_type: SnapshotType = snapshot_type.into();
|
||||
|
||||
snapshot_type
|
||||
.snapshot_data(image_snapshot)?
|
||||
.save(&path)
|
||||
.map_err(|_| mlua::Error::RuntimeError(format!("Failed to save snapshot data to {}", path)))
|
||||
}
|
||||
|
||||
fn copy_to_clipboard(_: &Lua, snapshot_type: SnapshotType) {}
|
||||
|
||||
#[mlua::lua_module(skip_memory_check)]
|
||||
fn codesnap_generator(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let exports = lua.create_table()?;
|
||||
|
||||
exports.set("save", lua.create_function(save)?)?;
|
||||
|
||||
Ok(exports)
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
use regex::Regex;
|
||||
use std::env::{var, VarError};
|
||||
|
||||
pub fn parse_save_path(path: String) -> Result<String, VarError> {
|
||||
let home_path = var("HOME")?;
|
||||
let regex = Regex::new(r"(~|$HOME)").unwrap();
|
||||
let path = regex.replace_all(&path, home_path);
|
||||
|
||||
Ok(path.to_string())
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
use crate::{config::TakeSnapshotParams, path::parse_save_path, snapshot::take_snapshot};
|
||||
use nvim_oxi::api;
|
||||
|
||||
// The function will be called as FFI on Lua side
|
||||
#[allow(dead_code)]
|
||||
pub fn save_snapshot(config: TakeSnapshotParams) -> Result<(), api::Error> {
|
||||
match &config.save_path {
|
||||
Some(path) => {
|
||||
if !path.ends_with(".png") {
|
||||
return Err(api::Error::Other(
|
||||
"The save_path must ends with .png".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let pixmap = take_snapshot(config.clone())?;
|
||||
let path = parse_save_path(path.to_string())
|
||||
.map_err(|err| api::Error::Other(err.to_string()))?;
|
||||
|
||||
pixmap
|
||||
.save_png(path)
|
||||
.map_err(|err| api::Error::Other(err.to_string()))
|
||||
}
|
||||
None => Err(api::Error::Other(
|
||||
"Cannot find 'save_path' in config".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use tiny_skia::Pixmap;
|
||||
|
||||
use crate::components::background::Background;
|
||||
use crate::components::breadcrumbs::Breadcrumbs;
|
||||
use crate::components::code_block::CodeBlock;
|
||||
use crate::components::container::Container;
|
||||
use crate::components::editor::code::Code;
|
||||
use crate::components::editor::mac_title_bar::MacTitleBar;
|
||||
use crate::components::highlight_code_block::HighlightCodeBlock;
|
||||
use crate::components::interface::component::ComponentContext;
|
||||
use crate::components::interface::render_error;
|
||||
use crate::components::line_number::LineNumber;
|
||||
use crate::components::rect::Rect;
|
||||
use crate::components::watermark::Watermark;
|
||||
use crate::config::TakeSnapshotParams;
|
||||
|
||||
// Scale the screenshot to 3 times its size
|
||||
const SCALE_FACTOR: f32 = 3.;
|
||||
const LINE_HEIGHT: f32 = 20.;
|
||||
const VIEW_WATERMARK_PADDING: f32 = 82.;
|
||||
|
||||
// The params is come from neovim instance
|
||||
pub fn take_snapshot(params: TakeSnapshotParams) -> render_error::Result<Pixmap> {
|
||||
let context = ComponentContext {
|
||||
scale_factor: SCALE_FACTOR,
|
||||
take_snapshot_params: Arc::new(params.clone()),
|
||||
};
|
||||
let background_padding = Background::parse_background_padding(
|
||||
params.bg_x_padding,
|
||||
params.bg_y_padding,
|
||||
params.bg_padding,
|
||||
);
|
||||
|
||||
// If vertical background padding is less than 82., should hidden watermark component
|
||||
// If watermark text is equal to "", the watermark component is hidden
|
||||
let watermark = if background_padding.bottom >= VIEW_WATERMARK_PADDING {
|
||||
params.watermark
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let pixmap = Container::from_children(vec![Box::new(Background::new(
|
||||
background_padding,
|
||||
vec![
|
||||
Box::new(Rect::new(
|
||||
16.,
|
||||
params.min_width,
|
||||
vec![
|
||||
Box::new(MacTitleBar::from_radius(8., params.mac_window_bar)),
|
||||
Box::new(Breadcrumbs::from_path(
|
||||
params.file_path,
|
||||
15.,
|
||||
params.breadcrumbs_separator,
|
||||
params.has_breadcrumbs,
|
||||
)),
|
||||
Box::new(CodeBlock::from_children(vec![
|
||||
Box::new(HighlightCodeBlock::from_line_number(
|
||||
params.highlight_start_line_number,
|
||||
params.highlight_end_line_number,
|
||||
LINE_HEIGHT,
|
||||
)),
|
||||
Box::new(LineNumber::new(
|
||||
¶ms.code,
|
||||
params.start_line_number,
|
||||
LINE_HEIGHT,
|
||||
)),
|
||||
Box::new(Code::new(params.code, LINE_HEIGHT, 15.)),
|
||||
])),
|
||||
],
|
||||
)),
|
||||
Box::new(Watermark::new(watermark)),
|
||||
],
|
||||
))])
|
||||
.draw_root(&context)?;
|
||||
|
||||
Ok(pixmap)
|
||||
}
|
12
generator/src/snapshot_config.rs
Normal file
12
generator/src/snapshot_config.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use codesnap::config::SnapshotConfig;
|
||||
use mlua::{FromLua, LuaSerdeExt};
|
||||
|
||||
pub struct SnapshotConfigLua(pub SnapshotConfig);
|
||||
|
||||
impl FromLua for SnapshotConfigLua {
|
||||
fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
|
||||
let config: SnapshotConfig = lua.from_value::<SnapshotConfig>(value)?;
|
||||
|
||||
Ok(SnapshotConfigLua(config))
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
use cosmic_text::{
|
||||
Align, Attrs, AttrsList, Buffer, BufferLine, Color, FontSystem, LineEnding, Metrics, Shaping,
|
||||
SwashCache,
|
||||
};
|
||||
use tiny_skia::{Paint, Pixmap, Rect, Transform};
|
||||
|
||||
pub struct FontRenderer {
|
||||
font_system: FontSystem,
|
||||
scale_factor: f32,
|
||||
metrics: Metrics,
|
||||
}
|
||||
|
||||
impl FontRenderer {
|
||||
pub fn new(
|
||||
font_size: f32,
|
||||
line_height: f32,
|
||||
scale_factor: f32,
|
||||
fonts_folder: &str,
|
||||
) -> FontRenderer {
|
||||
let mut font_system = FontSystem::new();
|
||||
|
||||
font_system.db_mut().load_fonts_dir(fonts_folder);
|
||||
|
||||
let metrics = Metrics::new(font_size, line_height).scale(scale_factor.clone());
|
||||
|
||||
FontRenderer {
|
||||
metrics,
|
||||
font_system,
|
||||
scale_factor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_text(
|
||||
&mut self,
|
||||
x: f32,
|
||||
y: f32,
|
||||
w: f32,
|
||||
h: f32,
|
||||
spans: Vec<(&str, Attrs)>,
|
||||
pixmap: &mut Pixmap,
|
||||
) {
|
||||
let mut buffer = Buffer::new(&mut self.font_system, self.metrics);
|
||||
buffer.set_size(
|
||||
&mut self.font_system,
|
||||
Some(w * self.scale_factor),
|
||||
Some(h * self.scale_factor),
|
||||
);
|
||||
buffer.set_rich_text(
|
||||
&mut self.font_system,
|
||||
spans,
|
||||
Attrs::new(),
|
||||
Shaping::Advanced,
|
||||
);
|
||||
self.draw(x, y, &mut buffer, pixmap);
|
||||
}
|
||||
|
||||
pub fn draw_line(
|
||||
&mut self,
|
||||
x: f32,
|
||||
y: f32,
|
||||
w: f32,
|
||||
h: f32,
|
||||
line: &str,
|
||||
attrs: Attrs,
|
||||
align: Option<Align>,
|
||||
pixmap: &mut Pixmap,
|
||||
) {
|
||||
let mut buffer = Buffer::new(&mut self.font_system, self.metrics);
|
||||
let mut line = if cfg!(unix) {
|
||||
BufferLine::new(
|
||||
line,
|
||||
LineEnding::Lf,
|
||||
AttrsList::new(attrs),
|
||||
Shaping::Advanced,
|
||||
)
|
||||
} else if cfg!(windows) {
|
||||
BufferLine::new(
|
||||
line,
|
||||
LineEnding::CrLf,
|
||||
AttrsList::new(attrs),
|
||||
Shaping::Advanced,
|
||||
)
|
||||
} else {
|
||||
panic!("Unsupported OS")
|
||||
};
|
||||
|
||||
line.set_align(align);
|
||||
buffer.lines = vec![line];
|
||||
buffer.set_size(&mut self.font_system, Some(w), Some(h));
|
||||
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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue