mirror of
https://github.com/Xevion/time-banner.git
synced 2025-12-06 01:16:36 -06:00
Project init : basic SVG rendering
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
1324
Cargo.lock
generated
Normal file
1324
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "time-banner"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
resvg = "0.34.1"
|
||||
axum = "0.6.18"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.68"
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
futures = "0.3.28"
|
||||
png = "0.17.9"
|
||||
21
README.md
Normal file
21
README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# time-banner
|
||||
|
||||
My first Rust project, intended to offer a simple way to display the current time relative, in an image format.
|
||||
|
||||
## Planned Features
|
||||
|
||||
- Dynamic light/dark mode
|
||||
- Via Query Parameters for Raster or SVG
|
||||
- Via CSS for SVG
|
||||
- Relative or Absolute Format
|
||||
- Dynamic Formats
|
||||
- Caching Abilities
|
||||
- Relative caching for up to 59 seconds, purged on the minute
|
||||
- Absolute caching for up to 50MB, purged on a LRU basis
|
||||
- Flexible & Dynamic Browser API
|
||||
- Allow users to play with format in numerous ways to query API
|
||||
- Examples
|
||||
- `/svg/2023-06-14-3PM-CST`
|
||||
- `2023-06-14-3PM-CST.svg`
|
||||
- `/jpeg/2023.06.14.33` (14th of June, 2023, 2:33 PM UTC)
|
||||
- `/jpeg/2023.06.14.33T-5` (14th of June, 2023, 2:33 PM UTC-5)
|
||||
47
src/main.rs
Normal file
47
src/main.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use axum::{http::StatusCode, Json, response::IntoResponse, Router, routing::{get, post}};
|
||||
use axum::body::{Bytes, Full};
|
||||
use axum::extract::ConnectInfo;
|
||||
use axum::http::header;
|
||||
use axum::response::Response;
|
||||
|
||||
mod svg;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// initialize tracing
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(root));
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
tracing::debug!("listening on {}", addr);
|
||||
axum::Server::bind(&addr)
|
||||
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// basic handler that responds with a static string
|
||||
async fn root(connect_info: ConnectInfo<SocketAddr>) -> impl IntoResponse {
|
||||
let raw_image = svg::get();
|
||||
|
||||
if raw_image.is_err() {
|
||||
return Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(Full::from(
|
||||
format!("Internal Server Error :: {}", raw_image.err().unwrap())
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, "image/x-png")
|
||||
.body(Full::from(raw_image.unwrap()))
|
||||
.unwrap()
|
||||
}
|
||||
50
src/svg.rs
Normal file
50
src/svg.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use png::EncodingError;
|
||||
use resvg::{tiny_skia, usvg};
|
||||
use resvg::usvg::{fontdb, TreeParsing, TreeTextToPath};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RenderError {
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RenderError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
if self.message.is_none() {
|
||||
write!(f, "RenderError")
|
||||
} else {
|
||||
write!(f, "RenderError: {}", self.message.as_ref().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get() -> Result<Vec<u8>, RenderError> {
|
||||
let rtree = {
|
||||
let mut opt = usvg::Options::default();
|
||||
// Get file's absolute directory.
|
||||
opt.resources_dir = std::fs::canonicalize("")
|
||||
.ok()
|
||||
.and_then(|p| p.parent().map(|p| p.to_path_buf()));
|
||||
|
||||
let mut fontdb = fontdb::Database::new();
|
||||
fontdb.load_system_fonts();
|
||||
|
||||
let svg_data = std::fs::read("test.svg").unwrap();
|
||||
// print bytes as string
|
||||
println!("{:?}", String::from_utf8(svg_data.clone()).unwrap());
|
||||
|
||||
let mut tree_result = usvg::Tree::from_data(&svg_data, &opt);
|
||||
if tree_result.is_err() { return Err(RenderError { message: Some("Failed to parse".to_string()) }); }
|
||||
|
||||
|
||||
let tree = tree_result.as_mut().unwrap();
|
||||
tree.convert_text(&fontdb);
|
||||
|
||||
resvg::Tree::from_usvg(&tree)
|
||||
};
|
||||
|
||||
let pixmap_size = rtree.size.to_int_size();
|
||||
let mut pixmap = tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();
|
||||
rtree.render(tiny_skia::Transform::default(), &mut pixmap.as_mut());
|
||||
|
||||
pixmap.encode_png().map_err(|_| RenderError { message: Some("Failed to encode".to_string()) })
|
||||
}
|
||||
Reference in New Issue
Block a user