Skip to content

Commit

Permalink
add support for rendering text into client
Browse files Browse the repository at this point in the history
  • Loading branch information
lilioid committed Apr 2, 2024
1 parent d025847 commit 76dea36
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 1 deletion.
32 changes: 32 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ ws = ["dep:tokio-tungstenite", "dep:futures-util"]
tcp = []
udp = []
windowing = ["dep:minifb"]
cli = ["tcp", "dep:clap", "dep:rand", "dep:tracing-subscriber", "dep:image"]
cli = ["tcp", "dep:clap", "dep:rand", "dep:tracing-subscriber", "dep:image", "dep:ab_glyph"]

[lib]
path = "src/lib.rs"
Expand All @@ -50,6 +50,7 @@ image = { version = "0.25.0", optional = true }
tracing-subscriber = { version = "0.3.17", optional = true }
clap = { version = "4.0.30", optional = true, features = [ "derive" ] }
url = "2.5.0"
ab_glyph = { version = "0.2.23", optional = true }

[dev-dependencies]
quickcheck = "1.0.3"
Expand Down
Binary file added resources/Hermit-Regular.otf
Binary file not shown.
16 changes: 16 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub(crate) enum Command {
PutRectangle(PutRectangleData),
/// Upload an image to a pixelflut server
PutImage(PutImageData),
/// Render a string onto the server (with transparent background)
PutText(PutTextOpts),
}

#[derive(Args, Debug, Clone)]
Expand Down Expand Up @@ -164,6 +166,20 @@ pub(crate) struct PutImageData {
pub path: PathBuf,
}

#[derive(Args, Debug, Clone)]
pub(crate) struct PutTextOpts {
#[command(flatten)]
pub common: CommonClientOps,

/// The text to draw
#[arg(short = 't', long = "text")]
pub text: String,

/// The color in which the text is rendered
#[arg(long = "color")]
pub color: TargetColor,
}

#[derive(Debug, Clone)]
pub(crate) enum TargetDimension {
/// Fill all available space
Expand Down
63 changes: 63 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![feature(never_type)]

use ab_glyph::{Font, FontRef};
use bytes::buf::Writer;
use bytes::{BufMut, BytesMut};
use clap::Parser;
Expand Down Expand Up @@ -38,6 +39,8 @@ use url::Url;
mod cli;
mod main_utils;

const FONT_HERMIT_REGULAR: &[u8] = include_bytes!("../resources/Hermit-Regular.otf");

#[tokio::main]
async fn main() {
let args = cli::CliOpts::parse();
Expand All @@ -51,6 +54,7 @@ async fn main() {
cli::Command::Server(opts) => start_server(opts).await,
cli::Command::PutRectangle(opts) => put_rectangle(opts).await,
cli::Command::PutImage(opts) => put_image(opts).await,
cli::Command::PutText(opts) => put_text(opts).await,
};
})
.await;
Expand Down Expand Up @@ -367,3 +371,62 @@ async fn put_image(opts: &cli::PutImageData) {
.run_loop(fill_buf, &opts.common, false)
.await;
}

async fn put_text(opts: &cli::PutTextOpts) {
let font = FontRef::try_from_slice(FONT_HERMIT_REGULAR).unwrap();

// define how a request buffer is filled
let fill_buf = |buf: &mut Writer<BytesMut>, x_min: usize, x_max: usize, y_min: usize, y_max: usize| {
// select a color
let color = match opts.color {
TargetColor::RandomPerIteration | TargetColor::RandomOnce => {
Color::from((random(), random(), random()))
}
TargetColor::Specific(c) => c,
};
tracing::debug!("Determined color to be #{color:X}");

// calculate scaling depending on requested text width (this is only possible because we use a monospace font)
let scaling = {
let glyph = font.glyph_id('_').with_scale(1.0);
let glyph_width = font.glyph_bounds(&glyph).width();
let target_width = (x_max - x_min) as f32 / opts.text.chars().count() as f32;
target_width / glyph_width
};
tracing::debug!("Determined font scaling to be {scaling}");

// iterate over all characters
tracing::debug!(
"Filling command-buffer to draw {:?} in #{color:X} from {x_min},{y_min} to {x_max},{y_max}",
opts.text
);
for (i, char) in opts.text.chars().enumerate() {
let glyph = font.glyph_id(char).with_scale(scaling);
let glyph_width = font.glyph_bounds(&glyph).width() as usize;

let outline = font.outline_glyph(glyph).unwrap();
outline.draw(|x, y, coverage| {
if coverage >= 0.5 {
Request::SetPixel {
x: x_min + (x as usize + i * glyph_width),
y: y_min + (y as usize),
color,
}
.write(buf)
.unwrap();
}
});
}
};

// run main client loop
main_utils::DynClient::connect(&opts.common.server)
.await
.expect("Could not connect to pixelflut server")
.run_loop(
fill_buf,
&opts.common,
matches!(opts.color, TargetColor::RandomPerIteration),
)
.await;
}

0 comments on commit 76dea36

Please sign in to comment.