initial commit

This commit is contained in:
Robert Perce 2026-02-26 19:31:45 -06:00
commit 1e7f8bc24d
6 changed files with 2953 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

2835
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

16
Cargo.toml Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

100
src/main.rs Normal file
View 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(())
}