Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/CascadiaCode-Bold.ttf
Binary file not shown.
Binary file added assets/CascadiaCode-BoldItalic.ttf
Binary file not shown.
Binary file added assets/CascadiaCode-Italic.ttf
Binary file not shown.
131 changes: 131 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/env python3

WIDTH = 79

# Reset all attributes
RESET = "\033[0m"

# Text styles
BOLD = "\033[1m"
DIM = "\033[2m"
ITALIC = "\033[3m" # May not be supported in all terminals.
UNDERLINE = "\033[4m" # Standard single underline
REVERSE = "\033[7m"
STRIKETHROUGH = "\033[9m" # Not supported in all terminal emulators.

# Extended underline styles (if supported)
# Using the extended SGR syntax for underline styles:
# Single: \033[4m (or \033[4:1m), Double: \033[4:2m, Curly: \033[4:3m, Dotted: \033[4:4m
DOUBLE_UNDERLINE = "\033[4:2m"
CURLY_UNDERLINE = "\033[4:3m"
DOTTED_UNDERLINE = "\033[4:4m"

# Basic palette colors (foreground)
BLACK = "\033[30m"
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
MAGENTA = "\033[35m"
CYAN = "\033[36m"
WHITE = "\033[37m"
BRIGHT_BLACK = "\033[90m"
BRIGHT_RED = "\033[91m"
BRIGHT_GREEN = "\033[92m"
BRIGHT_YELLOW = "\033[93m"
BRIGHT_BLUE = "\033[94m"
BRIGHT_MAGENTA = "\033[95m"
BRIGHT_CYAN = "\033[96m"
BRIGHT_WHITE = "\033[97m"

# Basic background colors
BG_BLACK = "\033[40m"
BG_RED = "\033[41m"
BG_GREEN = "\033[42m"
BG_YELLOW = "\033[43m"
BG_BLUE = "\033[44m"
BG_MAGENTA = "\033[45m"
BG_CYAN = "\033[46m"
BG_WHITE = "\033[47m"
BG_BRIGHT_BLACK = "\033[100m"
BG_BRIGHT_RED = "\033[101m"
BG_BRIGHT_GREEN = "\033[102m"
BG_BRIGHT_YELLOW = "\033[103m"
BG_BRIGHT_BLUE = "\033[104m"
BG_BRIGHT_MAGENTA = "\033[105m"
BG_BRIGHT_CYAN = "\033[106m"
BG_BRIGHT_WHITE = "\033[107m"

# 256-color palette
PITCH_BLACK = "\033[38;5;16m"
SNOW_WHITE = "\033[38;5;231m"

# List of text styles
print(f"{BOLD}Available Text Styles:{RESET}")
print(f"• Bold: {BOLD}This is bold text{RESET}")
print(f"• Dim: {DIM}This is dim text{RESET}")
print(f"• Italic: {ITALIC}This is italic text{RESET}")
print(f"• Underline: {UNDERLINE}This is underlined text{RESET}")
print(f"• Strikethrough: {STRIKETHROUGH}This is strikethrough text{RESET}")
print(f"• Reverse: {REVERSE}This is reversed text{RESET}")
print()

# Basic Foreground Colors
print(f"{BOLD}Basic Foreground Colors:{RESET}")
print("• Normal: ", end="")
print(f"{BLACK}Black{RESET} ", end="")
print(f"{RED}Red{RESET} ", end="")
print(f"{GREEN}Green{RESET} ", end="")
print(f"{YELLOW}Yellow{RESET} ", end="")
print(f"{BLUE}Blue{RESET} ", end="")
print(f"{MAGENTA}Magenta{RESET} ", end="")
print(f"{CYAN}Cyan{RESET} ", end="")
print(f"{WHITE}White{RESET}")
print("• Bright: ", end="")
print(f"{BRIGHT_BLACK}Black{RESET} ", end="")
print(f"{BRIGHT_RED}Red{RESET} ", end="")
print(f"{BRIGHT_GREEN}Green{RESET} ", end="")
print(f"{BRIGHT_YELLOW}Yellow{RESET} ", end="")
print(f"{BRIGHT_BLUE}Blue{RESET} ", end="")
print(f"{BRIGHT_MAGENTA}Magenta{RESET} ", end="")
print(f"{BRIGHT_CYAN}Cyan{RESET} ", end="")
print(f"{BRIGHT_WHITE}White{RESET}")
print()

# Mixing styles with colors
print(f"{BOLD}Mixed Styles with Colors:{RESET}")
print(f"• {BOLD}{RED}Bold red text{RESET}")
print(f"• {ITALIC}{GREEN}Italic green text{RESET}")
print(f"• {UNDERLINE}{BLUE}Underlined blue text{RESET}")
print(f"• {BOLD}{UNDERLINE}{MAGENTA}Bold underlined magenta text{RESET}")
print()

import sys

FG = PITCH_BLACK
if len(sys.argv) > 1 and sys.argv[1] == "light":
FG = SNOW_WHITE

# Basic Background Colors with contrasting text
print(f"{BOLD}Basic Background Colors:{RESET}")
print(f"{BG_BLACK}{SNOW_WHITE} Black BG {RESET}", end="")
print(f"{BG_RED}{FG} Red BG {RESET}", end="")
print(f"{BG_GREEN}{FG} Green BG {RESET}", end="")
print(f"{BG_YELLOW}{FG} Yellow BG {RESET}", end="")
print(f"{BG_BLUE}{FG} Blue BG {RESET}", end="")
print(f"{BG_MAGENTA}{FG} Magenta BG {RESET}", end="")
print(f"{BG_CYAN}{FG} Cyan BG {RESET}", end="")
print(f"{BG_WHITE}{PITCH_BLACK} White BG {RESET}")
print()

# 24-bit True Color Background Gradient (79 characters wide)
print(f"{BOLD}24-bit True Color Background:{RESET}")
# Loop WIDTH times to print exactly WIDTH blocks.
for i in range(WIDTH):
# Calculate red value decreasing from 255 to 0 across the gradient
red = int(255 - (255 * i / (WIDTH-1)) + 0.5)
# Calculate blue value increasing from 0 to 255 across the gradient
blue = int(255 * i / (WIDTH-1) + 0.5)
# Print one block (a space with the background color set) without a newline.
print(f"\033[48;2;{red};0;{blue}m \033[0m", end="")
print()
2 changes: 1 addition & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub struct Args {
#[arg(long, short = 'W', default_value = "auto")]
pub width: Dimension,

/// Final image height in terminal lines, or 'auto'
/// Final image height in terminal rows, or 'auto'
#[arg(long, short = 'H', default_value = "auto")]
pub height: Dimension,

Expand Down
18 changes: 5 additions & 13 deletions src/image_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use unicode_width::UnicodeWidthChar;
use crate::constants::{FONT_SIZE, IMAGE_QUALITY_MULTIPLIER};
use crate::image_renderer::canvas::Canvas;
use crate::image_renderer::render_size::{calculate_char_size, calculate_image_size};
use crate::image_renderer::utils::resolve_rgba_with_palette;
use crate::window_decoration::{WindowDecoration, WindowMetrics};

pub mod canvas;
Expand Down Expand Up @@ -74,7 +73,7 @@ impl ImageRenderer {
let font = window_decoration.font()?;

let scale = PxScale::from((FONT_SIZE * IMAGE_QUALITY_MULTIPLIER) as f32);
let char_size = calculate_char_size(font, scale);
let char_size = calculate_char_size(&font.regular, scale);
let command_line = window_decoration.build_command_line(&command.join(" "));

let metrics = window_decoration.compute_metrics(char_size);
Expand Down Expand Up @@ -113,7 +112,6 @@ impl ImageRenderer {
self.metrics.border_width + self.metrics.title_bar_height + self.metrics.padding;

let color_palette = self.window_decoration.get_color_palette();
let default_fg_color = color_palette[7];

let command_line = self
.window_decoration
Expand All @@ -125,11 +123,9 @@ impl ImageRenderer {
let x = i32::try_from(start_x + x_offset)?;

let text = cell.str();
let color = resolve_rgba_with_palette(&color_palette, cell.attrs().foreground())
.unwrap_or(default_fg_color);
let background = resolve_rgba_with_palette(&color_palette, cell.attrs().background());

self.canvas.draw_text(text, x, y, color, background);
self.canvas
.draw_text(text, x, y, &color_palette, cell.attrs());

let text_width = text
.chars()
Expand All @@ -147,7 +143,6 @@ impl ImageRenderer {
self.metrics.border_width + self.metrics.title_bar_height + self.metrics.padding;

let color_palette = self.window_decoration.get_color_palette();
let default_fg_color = color_palette[7];

for (row_idx, line) in screen.screen_lines().iter().enumerate() {
let row_idx = u32::try_from(row_idx + 1)?;
Expand All @@ -158,12 +153,9 @@ impl ImageRenderer {
let x = i32::try_from(start_x + x_offset)?;

let text = cell.str();
let color = resolve_rgba_with_palette(&color_palette, cell.attrs().foreground())
.unwrap_or(default_fg_color);
let background =
resolve_rgba_with_palette(&color_palette, cell.attrs().background());

self.canvas.draw_text(text, x, y, color, background);
self.canvas
.draw_text(text, x, y, &color_palette, cell.attrs());

let text_width = text
.chars()
Expand Down
111 changes: 87 additions & 24 deletions src/image_renderer/canvas.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
use crate::image_renderer::{
render_size::{calculate_char_size, Size},
ImageRendererError,
use crate::{
image_renderer::{
render_size::{calculate_char_size, Size},
utils::resolve_rgba_with_palette,
ImageRendererError,
},
window_decoration::Fonts,
};
use ab_glyph::{FontArc, PxScale};
use ab_glyph::PxScale;
use image::{Rgba, RgbaImage};
use imageproc::drawing::draw_text_mut;
use termwiz::cell::{CellAttributes, Intensity, Underline};
use tiny_skia::{Color, FillRule, Paint, PathBuilder, Pixmap, Rect, Transform};
use tracing::warn;

Expand All @@ -22,7 +27,7 @@ bitflags::bitflags! {
pub struct Canvas {
pixmap: Pixmap,
image_for_text: RgbaImage,
font: FontArc,
font: Fonts,
scale: PxScale,
char_size: Size,
}
Expand All @@ -31,12 +36,12 @@ impl Canvas {
pub fn new(
width: u32,
height: u32,
font: FontArc,
font: Fonts,
scale: PxScale,
) -> Result<Self, ImageRendererError> {
let pixmap = Pixmap::new(width, height).ok_or(ImageRendererError::CanvasInitFailed)?;
let image_for_text = RgbaImage::new(width, height);
let char_size = calculate_char_size(&font, scale);
let char_size = calculate_char_size(&font.regular, scale);

Ok(Self {
pixmap,
Expand Down Expand Up @@ -162,10 +167,56 @@ impl Canvas {
text: &str,
x: i32,
y: i32,
color: Rgba<u8>,
background: Option<Rgba<u8>>,
color_palette: &[Rgba<u8>; 256],
attributes: &CellAttributes,
) {
let foreground_color = if attributes.reverse() {
color_palette[0]
} else {
resolve_rgba_with_palette(color_palette, attributes.foreground())
.unwrap_or(color_palette[7])
};

let is_bold = matches!(attributes.intensity(), Intensity::Bold);

let font = if is_bold && attributes.italic() {
&self.font.bold_italic
} else if is_bold {
&self.font.bold
} else if attributes.italic() {
&self.font.italic
} else {
&self.font.regular
};

draw_text_mut(
&mut self.image_for_text,
foreground_color,
x,
y,
self.scale,
&font,
text,
);

self.draw_cell_attributes(text, x, y, color_palette, attributes);
}

pub fn draw_cell_attributes(
&mut self,
text: &str,
x: i32,
y: i32,
color_palette: &[Rgba<u8>; 256],
attributes: &CellAttributes,
) {
if let Some(bg_color) = background {
let bg_color = if attributes.reverse() {
resolve_rgba_with_palette(color_palette, attributes.foreground())
.or(Some(color_palette[7]))
} else {
resolve_rgba_with_palette(color_palette, attributes.background())
};
if let Some(bg_color) = bg_color {
self.fill_rect(
x,
y,
Expand All @@ -175,15 +226,28 @@ impl Canvas {
);
}

draw_text_mut(
&mut self.image_for_text,
color,
x,
y,
self.scale,
&self.font,
text,
);
if attributes.underline() != Underline::None {
let underline_color =
resolve_rgba_with_palette(color_palette, attributes.underline_color())
.unwrap_or(color_palette[7]);
self.fill_rect(
x,
y + self.char_height() as i32,
text.chars().count() as u32 * self.char_width(),
1,
underline_color,
);
}

if attributes.strikethrough() {
self.fill_rect(
x,
y + (self.char_height() as i32 / 2),
text.chars().count() as u32 * self.char_width(),
1,
color_palette[7],
);
}
}

pub fn width(&self) -> u32 {
Expand Down Expand Up @@ -229,13 +293,12 @@ impl Canvas {

#[cfg(test)]
mod tests {
use crate::window_decoration::common::default_font;
use crate::window_decoration::common::{default_font, get_default_color_palette};

use super::*;
use ab_glyph::FontArc;
use image::Rgba;

fn make_font() -> FontArc {
fn make_font() -> Fonts {
default_font().unwrap().clone()
}

Expand Down Expand Up @@ -267,8 +330,8 @@ mod tests {
"Hello",
5,
5,
Rgba([0, 0, 0, 255]),
Some(Rgba([255, 255, 255, 255])),
&get_default_color_palette(),
&CellAttributes::default(),
);
}

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ mod terminal_builder;
mod window_decoration;

pub use app::{run_shellshot, Args};
pub use window_decoration::WindowDecorationType;
Loading