Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
- "png"
- "pdf"
- "png,pdf"
- "png,net"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Cargo.lock
# Test outputs
output.png
output.pdf
remote_image.png

# OS files
.DS_Store
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Remote resource loading via the optional `net` feature (enabled by default),
powered by [`blitz-net`]. Images, linked stylesheets, `@import`s, and web
fonts referenced by the HTML are now fetched and applied before rendering.
- Supported URL schemes: `http(s)://`, `file://`, and `data:`.
- `render` remains synchronous: it transparently uses a shared, process-wide
Tokio runtime (created once and reused across calls) and blocks until all
resources are loaded (subject to an internal timeout).
- `remote_image` example demonstrating remote image loading.
- Offline integration test (`tests/render_net.rs`) covering resource loading
via `data:` URIs.

### Changed

- Disabling the `net` feature builds with no async or TLS dependencies.

### Notes

- The `net` feature uses `reqwest` with the native TLS backend, which requires
OpenSSL development headers at build time.

[`blitz-net`]: https://crates.io/crates/blitz-net

## [0.1.0] - 2024-01-29

### Added
Expand Down
13 changes: 12 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ keywords = ["html", "pdf", "png", "render", "headless"]
categories = ["rendering", "graphics", "multimedia::images"]

[features]
default = ["png", "pdf"]
default = ["png", "pdf", "net"]
png = ["dep:anyrender", "dep:anyrender_vello_cpu", "dep:png"]
pdf = ["dep:krilla", "dep:stylo", "dep:parley", "dep:linebender_resource_handle"]
# Fetch remote resources (images, stylesheets, fonts) over http(s)/file/data URLs
net = ["dep:blitz-net", "dep:tokio"]

[dependencies]
# Core HTML/CSS parsing and layout (always required)
Expand All @@ -33,6 +35,10 @@ stylo = { version = "0.8", optional = true } # For accessing computed styles in
parley = { version = "0.6", optional = true } # For text layout types
linebender_resource_handle = { version = "0.1", optional = true } # For font data types

# Network resource loading (optional, enabled by default)
blitz-net = { version = "0.2.1", optional = true }
tokio = { version = "1", optional = true, features = ["rt-multi-thread", "sync", "time"] }

# Common dependencies
thiserror = "2"

Expand All @@ -52,3 +58,8 @@ path = "examples/simple.rs"
[[example]]
name = "from_file"
path = "examples/from_file.rs"

[[example]]
name = "remote_image"
path = "examples/remote_image.rs"
required-features = ["net"]
47 changes: 45 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ A Chromium-free HTML rendering engine for generating PNG and PDF outputs in pure
- **PNG output** — High-quality raster images via CPU-based rendering
- **PDF output** — Vector PDF documents with embedded fonts
- **Modern CSS** — Flexbox, Grid, and common CSS properties via Stylo (Firefox's CSS engine)
- **Remote resources** — Fetch images, stylesheets, and web fonts over `http(s)`/`file`/`data` URLs (optional `net` feature, enabled by default)
- **Simple API** — Single function call to render HTML to bytes

## Installation
Expand All @@ -23,9 +24,18 @@ Or with specific features:

```toml
[dependencies]
# Just PNG, no networking (no async/TLS dependencies)
hyper-render = { version = "0.1", default-features = false, features = ["png"] }
```

Available features (all enabled by default):

| Feature | Description |
|---------|-------------|
| `png` | PNG output via the Vello CPU rasterizer |
| `pdf` | PDF output via Krilla |
| `net` | Fetch remote resources referenced by the HTML |

## Quick Start

```rust
Expand Down Expand Up @@ -90,6 +100,33 @@ let config = Config::new()
| `OutputFormat::Png` | ✅ Full | Raster image via Vello CPU renderer |
| `OutputFormat::Pdf` | ✅ Full | Vector PDF with embedded fonts and backgrounds |

### Network Resources

When the `net` feature is enabled (the default), resources referenced by the
HTML — `<img src>`, `<link rel="stylesheet">`, `@import`, and `@font-face` — are
fetched before rendering. The following URL schemes are supported:

- `http://` and `https://` — fetched over the network
- `file://` — read from the local filesystem
- `data:` — decoded inline

```rust
use hyper_render::{render, Config};

let html = r#"<img src="https://example.com/logo.png" />"#;
let png = render(html, Config::default())?; // logo is fetched and painted
# Ok::<(), hyper_render::Error>(())
```

Resource loading is synchronous from the caller's perspective: `render` blocks
until all referenced resources have been fetched and applied (subject to an
internal timeout), so no async runtime is required by your code.

> **TLS note:** networking uses `reqwest` with the native TLS backend, which
> requires OpenSSL development headers at build time (e.g. `libssl-dev` on
> Debian/Ubuntu, `openssl-devel` on Fedora). To build without any networking,
> async, or TLS dependencies, disable the `net` feature.

## Try It Yourself

### Clone and Build
Expand All @@ -108,6 +145,12 @@ cargo run --example simple

This generates `output.png` and `output.pdf` in the current directory.

To see remote resource loading in action (requires network + the `net` feature):

```bash
cargo run --example remote_image
```

### Render Your Own HTML

```bash
Expand Down Expand Up @@ -235,8 +278,7 @@ cargo run --example from_file -- input.html output.png 2>/dev/null
## Limitations

- **JavaScript** — Not supported (by design)
- **Web fonts** — System fonts only; `@font-face` not yet supported
- **Images** — External image loading not yet implemented
- **Networking** — Remote resource loading requires the `net` feature; with it disabled, only inline (`data:`-free) content is rendered
- **Some CSS** — Advanced features like `position: sticky`, complex transforms may not work

## Dependencies
Expand All @@ -247,6 +289,7 @@ Core rendering stack:
- [Taffy](https://github.com/DioxusLabs/taffy) — Flexbox/Grid layout
- [Vello](https://github.com/linebender/vello) — 2D graphics (CPU renderer)
- [Krilla](https://github.com/LaurenzV/krilla) — PDF generation
- [blitz-net](https://github.com/DioxusLabs/blitz) — Resource fetching (`net` feature)

## License

Expand Down
43 changes: 43 additions & 0 deletions examples/remote_image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! Example demonstrating remote resource loading (the `net` feature).
//!
//! With the `net` feature enabled (on by default), hyper-render fetches
//! external resources referenced by the HTML — here an image loaded over
//! HTTPS — before rendering.
//!
//! Run with: `cargo run --example remote_image`
//!
//! Note: this example requires network access and the `net` feature. Build
//! without networking using `--no-default-features --features png`.

use hyper_render::{render, Config};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let html = r#"
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 32px; background: #0f172a; }
.card { background: #1e293b; border-radius: 16px; padding: 24px; max-width: 360px; }
h1 { color: #e2e8f0; font-size: 22px; margin: 0 0 16px 0; }
img { display: block; border-radius: 12px; width: 100%; height: auto; }
</style>
</head>
<body>
<div class="card">
<h1>Fetched over HTTPS</h1>
<img src="https://picsum.photos/id/237/360/240" alt="Remote image" />
</div>
</body>
</html>
"#;

println!("Rendering (fetching remote image)...");
let config = Config::new().size(440, 360).scale(2.0);
let png_bytes = render(html, config)?;

std::fs::write("remote_image.png", &png_bytes)?;
println!("Saved remote_image.png ({} bytes)", png_bytes.len());

Ok(())
}
39 changes: 36 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,19 @@

mod config;
mod error;
#[cfg(feature = "net")]
mod net;
mod render;

pub use config::{ColorScheme, Config, OutputFormat};
pub use error::{Error, Result};

use std::sync::Arc;

use blitz_dom::net::Resource;
use blitz_dom::DocumentConfig;
use blitz_html::HtmlDocument;
use blitz_traits::net::NetProvider;
use blitz_traits::shell::Viewport;

/// Render HTML content to the specified output format.
Expand Down Expand Up @@ -104,12 +110,34 @@ pub fn render(html: &str, config: Config) -> Result<Vec<u8>> {
// Validate configuration
config.validate()?;

// Set up networking so external resources (images, stylesheets, fonts) can
// be fetched. The provider must exist before parsing so requests triggered
// during parsing/layout are dispatched.
#[cfg(feature = "net")]
let mut net_env = net::NetEnv::new()?;

let net_provider: Option<Arc<dyn NetProvider<Resource>>> = {
#[cfg(feature = "net")]
{
Some(net_env.provider())
}
#[cfg(not(feature = "net"))]
{
None
}
};

// Parse HTML and create document
let mut document = create_document(html, &config)?;
let mut document = create_document(html, &config, net_provider)?;

// Resolve styles and compute layout
// Resolve styles and compute layout. This dispatches the initial batch of
// resource requests for linked stylesheets, images, etc.
document.resolve(0.0);

// Block until all fetched resources have been applied to the document.
#[cfg(feature = "net")]
net_env.load_resources(&mut document);

// Render to the specified format
match config.format {
OutputFormat::Png => render::png::render_to_png(&document, &config),
Expand Down Expand Up @@ -156,7 +184,11 @@ pub fn render_to_pdf(html: &str, config: Config) -> Result<Vec<u8>> {
}

/// Create and configure a Blitz document from HTML.
fn create_document(html: &str, config: &Config) -> Result<HtmlDocument> {
fn create_document(
html: &str,
config: &Config,
net_provider: Option<Arc<dyn NetProvider<Resource>>>,
) -> Result<HtmlDocument> {
let viewport = Viewport::new(
config.width,
config.height,
Expand All @@ -166,6 +198,7 @@ fn create_document(html: &str, config: &Config) -> Result<HtmlDocument> {

let doc_config = DocumentConfig {
viewport: Some(viewport),
net_provider,
..Default::default()
};

Expand Down
Loading