Project init : basic SVG rendering

This commit is contained in:
2023-06-16 19:52:56 -05:00
commit 08344b9af6
7 changed files with 1463 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

1324
Cargo.lock generated Normal file
View File

File diff suppressed because it is too large Load Diff

17
Cargo.toml Normal file
View 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
View 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
View 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
View 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()) })
}

3
test.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="256" height="32" xmlns="http://www.w3.org/2000/svg" font-family="Arial" font-size="32">
<text x="10" y="32">Simple rect</text>
</svg>

After

Width:  |  Height:  |  Size: 149 B