initial commit
This commit is contained in:
commit
1e7f8bc24d
6 changed files with 2953 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
2835
Cargo.lock
generated
Normal file
2835
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "kdl"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.102"
|
||||||
|
axum = { version = "0.8.8", features = ["macros"] }
|
||||||
|
chrono = "0.4.44"
|
||||||
|
headless_chrome = "1.0.21"
|
||||||
|
image = { version = "0.25.9", features = ["png"] }
|
||||||
|
maud = "0.27.0"
|
||||||
|
tempfile = "3.26.0"
|
||||||
|
tokio = { version = "1.49.0", features = ["fs", "rt", "tracing"] }
|
||||||
|
tower-http = { version = "0.6.8", features = ["fs", "trace"] }
|
||||||
|
tracing = "0.1.44"
|
||||||
1
kindle.html
Normal file
1
kindle.html
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<body><div>Hello banana</div><div>It is 2026-02-26 16:33:19.778042362 UTC</div></body>
|
||||||
BIN
kindle.png
Normal file
BIN
kindle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
100
src/main.rs
Normal file
100
src/main.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
use axum::Router;
|
||||||
|
use axum::response::{IntoResponse, Response};
|
||||||
|
use axum::routing::get;
|
||||||
|
use image::DynamicImage;
|
||||||
|
use image::codecs::png::{PngDecoder, PngEncoder};
|
||||||
|
use maud::html;
|
||||||
|
use std::io::{Cursor, Write};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AppError(anyhow::Error);
|
||||||
|
|
||||||
|
impl IntoResponse for AppError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
(
|
||||||
|
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("Something went wrong: {}", self.0),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<E> From<E> for AppError
|
||||||
|
where
|
||||||
|
E: Into<anyhow::Error>,
|
||||||
|
{
|
||||||
|
fn from(err: E) -> Self {
|
||||||
|
Self(err.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
async fn gen_png() -> Result<impl axum::response::IntoResponse, AppError> {
|
||||||
|
let now = chrono::prelude::Utc::now().to_string();
|
||||||
|
let markup = html! {
|
||||||
|
body {
|
||||||
|
div { "Hello banana" }
|
||||||
|
div { (format!("It is {}", now)) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tempfile = tempfile::Builder::new()
|
||||||
|
.suffix(".html")
|
||||||
|
.rand_bytes(5)
|
||||||
|
.tempfile()?;
|
||||||
|
|
||||||
|
tempfile
|
||||||
|
.write_all(markup.into_string().as_bytes())
|
||||||
|
.expect("unable to write");
|
||||||
|
|
||||||
|
tempfile.as_file().sync_all()?;
|
||||||
|
|
||||||
|
let launch = headless_chrome::browser::LaunchOptions::default_builder()
|
||||||
|
.window_size(Some((600, 939)))
|
||||||
|
.build()?;
|
||||||
|
let browser = headless_chrome::Browser::new(launch)?;
|
||||||
|
let tab = browser.new_tab()?;
|
||||||
|
let view = tab
|
||||||
|
.navigate_to(&format!("file://{}", tempfile.path().to_string_lossy()))?
|
||||||
|
.wait_for_element("body")?
|
||||||
|
.get_box_model()?
|
||||||
|
.margin_viewport();
|
||||||
|
let png_data = tab.capture_screenshot(
|
||||||
|
headless_chrome::protocol::cdp::Page::CaptureScreenshotFormatOption::Png,
|
||||||
|
None,
|
||||||
|
Some(view),
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let png = PngDecoder::new(Cursor::new(png_data))?;
|
||||||
|
let color = DynamicImage::from_decoder(png)?;
|
||||||
|
let gray = color.into_luma8();
|
||||||
|
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
let png_encoder = PngEncoder::new(&mut bytes);
|
||||||
|
gray.write_with_encoder(png_encoder)?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
axum::response::AppendHeaders([(axum::http::header::CONTENT_TYPE, "image/png")]),
|
||||||
|
bytes,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
async fn serve(port: &u32) -> Result<(), AppError> {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/kindle.png", get(gen_png))
|
||||||
|
//.nest_service("/kindle.png", gen_png()?)
|
||||||
|
.layer(tower_http::trace::TraceLayer::new_for_http());
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await?;
|
||||||
|
println!("Starting axum on 0.0.0.0:{}...", port);
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn main() -> Result<(), AppError> {
|
||||||
|
serve(&7777).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue