himg 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/Cargo.lock +4230 -0
- data/Cargo.toml +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +53 -0
- data/Rakefile +20 -0
- data/Steepfile +29 -0
- data/ext/himg/Cargo.lock +4019 -0
- data/ext/himg/Cargo.toml +34 -0
- data/ext/himg/build.rs +5 -0
- data/ext/himg/examples/assets/github_profile.html +119 -0
- data/ext/himg/examples/file.rs +61 -0
- data/ext/himg/extconf.rb +6 -0
- data/ext/himg/output/.gitkeep +0 -0
- data/ext/himg/src/html_to_image.rs +87 -0
- data/ext/himg/src/image_size.rs +20 -0
- data/ext/himg/src/lib.rs +68 -0
- data/ext/himg/src/logger.rs +45 -0
- data/ext/himg/src/options.rs +10 -0
- data/ext/himg/src/writer.rs +33 -0
- data/lib/himg/version.rb +5 -0
- data/lib/himg.rb +19 -0
- data/sig/himg.rbs +8 -0
- data/test.html.rbd +5 -0
- metadata +85 -0
data/ext/himg/Cargo.toml
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
[package]
|
2
|
+
name = "himg"
|
3
|
+
description = "ruby bindings to expose a blitz html->png pipeline"
|
4
|
+
version = "0.0.1"
|
5
|
+
edition = "2024"
|
6
|
+
authors = ["James Edwards-Jones <git@jamedjo.co.uk>"]
|
7
|
+
license = "MIT"
|
8
|
+
publish = false
|
9
|
+
|
10
|
+
[lib]
|
11
|
+
path = "src/lib.rs"
|
12
|
+
crate-type = ["cdylib", "rlib"]
|
13
|
+
|
14
|
+
[dependencies]
|
15
|
+
magnus = { version = "0.7.1" }
|
16
|
+
rb-sys = { version = "0.9", features = ["stable-api-compiled-fallback"] }
|
17
|
+
blitz-traits = { version = "0.1.0-alpha.1" }
|
18
|
+
blitz-dom = { version = "0.1.0-alpha.1" }
|
19
|
+
blitz-net = { version = "0.1.0-alpha.1" }
|
20
|
+
blitz-renderer-vello = { version = "0.1.0-alpha.1" }
|
21
|
+
blitz-html = { version = "0.1.0-alpha.1" }
|
22
|
+
tokio = { version = "1.42", features = ["rt-multi-thread", "macros"] }
|
23
|
+
png = "0.17"
|
24
|
+
|
25
|
+
#euclid = { workspace = true }
|
26
|
+
#image = { workspace = true }
|
27
|
+
#env_logger = "0.11"
|
28
|
+
#tracing-subscriber = "0.3"
|
29
|
+
|
30
|
+
[build-dependencies]
|
31
|
+
rb-sys-env = "0.2.2"
|
32
|
+
|
33
|
+
[dev-dependencies]
|
34
|
+
rb-sys-test-helpers = { version = "0.2.2" }
|
data/ext/himg/build.rs
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head><style type="text/css">.turbo-progress-bar {
|
4
|
+
position: fixed;
|
5
|
+
display: block;
|
6
|
+
top: 0;
|
7
|
+
left: 0;
|
8
|
+
height: 3px;
|
9
|
+
background: #0076ff;
|
10
|
+
z-index: 2147483647;
|
11
|
+
transition:
|
12
|
+
width 300ms ease-out,
|
13
|
+
opacity 150ms 150ms ease-in;
|
14
|
+
transform: translate3d(0, 0, 0);
|
15
|
+
}
|
16
|
+
</style>
|
17
|
+
<meta charset="utf-8">
|
18
|
+
|
19
|
+
|
20
|
+
<link rel="stylesheet" href="https://github.githubassets.com/assets/light-0cfd1fd8509e.css">
|
21
|
+
<link data-color-theme="dark" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark-d782f59290e2.css">
|
22
|
+
<link data-color-theme="dark_dimmed" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed-f9fbc4b99a77.css">
|
23
|
+
<link data-color-theme="dark_high_contrast" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_high_contrast-cff1c9b27b1a.css">
|
24
|
+
<link data-color-theme="dark_colorblind" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind-70097f75aec1.css">
|
25
|
+
<link data-color-theme="light_colorblind" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind-c2f0d49bdcd9.css">
|
26
|
+
<link data-color-theme="light_high_contrast" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_high_contrast-4747d7bc0bc4.css">
|
27
|
+
<link data-color-theme="light_tritanopia" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia-d3f6a61c91c8.css">
|
28
|
+
<link data-color-theme="dark_tritanopia" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia-a188d53f44bb.css">
|
29
|
+
|
30
|
+
<link rel="stylesheet" href="https://github.githubassets.com/assets/primer-primitives-953961b66e63.css">
|
31
|
+
<link rel="stylesheet" href="https://github.githubassets.com/assets/primer-4430d3c2c150.css">
|
32
|
+
<link rel="stylesheet" href="https://github.githubassets.com/assets/global-1d3440e946dd.css">
|
33
|
+
<link rel="stylesheet" href="https://github.githubassets.com/assets/github-e72829f5538b.css">
|
34
|
+
<link rel="stylesheet" href="https://github.githubassets.com/assets/staff-0aa284f91bc2.css">
|
35
|
+
<link rel="stylesheet" href="https://github.githubassets.com/assets/devtools-ed3c56d5f6b2.css">
|
36
|
+
<link rel="stylesheet" href="https://github.githubassets.com/assets/profile-a657309cdf66.css">
|
37
|
+
<link rel="stylesheet" href="https://github.githubassets.com/assets/insights-c5cddd751d33.css">
|
38
|
+
|
39
|
+
<title>nicoburns (Nico Burns)</title>
|
40
|
+
|
41
|
+
<meta name="viewport" content="width=device-width">
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
<meta name="description" content="nicoburns has 79 repositories available. Follow their code on GitHub.">
|
46
|
+
|
47
|
+
<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">
|
48
|
+
|
49
|
+
<meta name="hostname" content="github.com">
|
50
|
+
|
51
|
+
<meta name="expected-hostname" content="github.com">
|
52
|
+
<link rel="mask-icon" href="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" color="#000000">
|
53
|
+
<link rel="alternate icon" class="js-site-favicon" type="image/png" href="https://github.githubassets.com/favicons/favicon.png">
|
54
|
+
<link rel="icon" class="js-site-favicon" type="image/svg+xml" href="https://github.githubassets.com/favicons/favicon.svg" data-base-href="https://github.githubassets.com/favicons/favicon">
|
55
|
+
|
56
|
+
<meta name="theme-color" content="#1e2327">
|
57
|
+
<meta name="color-scheme" content="light dark">
|
58
|
+
|
59
|
+
</head>
|
60
|
+
<body>
|
61
|
+
<div class="Box d-flex p-3 width-full public source">
|
62
|
+
<div class="pinned-item-list-item-content">
|
63
|
+
<div class="d-flex width-full position-relative">
|
64
|
+
<div class="flex-1">
|
65
|
+
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-repo mr-1 color-fg-muted">
|
66
|
+
<path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z"></path>
|
67
|
+
</svg>
|
68
|
+
<span data-view-component="true" class="position-relative"><a data-hydro-click="{"event_type":"user_profile.click","payload":{"profile_user_id":1007307,"target":"PINNED_REPO","user_id":1007307,"originating_url":"https://github.com/nicoburns"}}" data-hydro-click-hmac="95bab3b0debdfa7d41ff86f71264a58e15a16e2c317afd1444c387724a44f62d" id="464340188" href="/DioxusLabs/blitz" data-view-component="true" class="Link mr-1 text-bold wb-break-word" aria-labelledby="tooltip-7d61525f-6eaa-4bf0-a1c6-b8ec7d17b15b"><span class="owner text-normal">DioxusLabs/</span><span class="repo">blitz</span></a> <tool-tip id="tooltip-7d61525f-6eaa-4bf0-a1c6-b8ec7d17b15b" for="464340188" popover="manual" data-direction="s" data-type="label" data-view-component="true" class="position-absolute sr-only" aria-hidden="true" role="tooltip" style="--tool-tip-position-top: 1239.265625px; --tool-tip-position-left: 500.75390625px;">DioxusLabs/blitz</tool-tip></span> <span></span><span class="Label Label--secondary v-align-middle mt-1 no-wrap v-align-baseline Label--inline">Public</span>
|
69
|
+
</div>
|
70
|
+
<div>
|
71
|
+
<input type="hidden" name="pinned_items_id_and_type[]" id="pinned-item-reorder-464340188" value="464340188-Repository" autocomplete="off" class="form-control">
|
72
|
+
<span role="button" class="pinned-item-handle js-pinned-item-reorder" aria-label="Drag to reorder">
|
73
|
+
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-grabber">
|
74
|
+
<path d="M10 13a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm0-4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm-4 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm5-9a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM7 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM6 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path>
|
75
|
+
</svg>
|
76
|
+
</span>
|
77
|
+
<button data-direction="up" type="button" data-view-component="true" class="show-on-focus sortable-button js-sortable-button Button--secondary Button--small Button right-0"> <span class="Button-content">
|
78
|
+
<span class="Button-label"><svg aria-label="Move blitz up" role="img" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-chevron-up">
|
79
|
+
<path d="M3.22 10.53a.749.749 0 0 1 0-1.06l4.25-4.25a.749.749 0 0 1 1.06 0l4.25 4.25a.749.749 0 1 1-1.06 1.06L8 6.811 4.28 10.53a.749.749 0 0 1-1.06 0Z"></path>
|
80
|
+
</svg></span>
|
81
|
+
</span>
|
82
|
+
</button>
|
83
|
+
<button data-direction="down" type="button" data-view-component="true" class="show-on-focus sortable-button js-sortable-button Button--secondary Button--small Button right-0"> <span class="Button-content">
|
84
|
+
<span class="Button-label"><svg aria-label="Move blitz down" role="img" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-chevron-down">
|
85
|
+
<path d="M12.78 5.22a.749.749 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.06 0L3.22 6.28a.749.749 0 1 1 1.06-1.06L8 8.939l3.72-3.719a.749.749 0 0 1 1.06 0Z"></path>
|
86
|
+
</svg></span>
|
87
|
+
</span>
|
88
|
+
</button>
|
89
|
+
</div>
|
90
|
+
</div>
|
91
|
+
|
92
|
+
|
93
|
+
<p class="pinned-item-desc color-fg-muted text-small mt-2 mb-0">
|
94
|
+
A radically modular HTML/CSS rendering engine
|
95
|
+
</p>
|
96
|
+
|
97
|
+
<p class="mb-0 mt-2 f6 color-fg-muted">
|
98
|
+
<span class="d-inline-block mr-3">
|
99
|
+
<span class="repo-language-color" style="background-color: #dea584"></span>
|
100
|
+
<span itemprop="programmingLanguage">Rust</span>
|
101
|
+
</span>
|
102
|
+
|
103
|
+
<a href="/DioxusLabs/blitz/stargazers" class="pinned-item-meta Link--muted">
|
104
|
+
<svg aria-label="stars" role="img" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-star">
|
105
|
+
<path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z"></path>
|
106
|
+
</svg>
|
107
|
+
2.2k
|
108
|
+
</a>
|
109
|
+
<a href="/DioxusLabs/blitz/forks" class="pinned-item-meta Link--muted">
|
110
|
+
<svg aria-label="forks" role="img" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-repo-forked">
|
111
|
+
<path d="M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z"></path>
|
112
|
+
</svg>
|
113
|
+
50
|
114
|
+
</a>
|
115
|
+
</p>
|
116
|
+
</div>
|
117
|
+
</div>
|
118
|
+
</body>
|
119
|
+
</html>
|
@@ -0,0 +1,61 @@
|
|
1
|
+
//! Load first CLI argument as a path. Fallback to hardcoded file if no CLI argument is provided.
|
2
|
+
|
3
|
+
use himg::{html_to_image, Options, ImageSize, write_png};
|
4
|
+
use himg::logger::{Logger, TimedLogger};
|
5
|
+
|
6
|
+
use blitz_traits::{ColorScheme};
|
7
|
+
use std::{
|
8
|
+
fs::File,
|
9
|
+
path::{Path, PathBuf},
|
10
|
+
};
|
11
|
+
|
12
|
+
#[tokio::main]
|
13
|
+
async fn main() {
|
14
|
+
let mut logger = TimedLogger::init();
|
15
|
+
|
16
|
+
let path_string = std::env::args()
|
17
|
+
.skip(1)
|
18
|
+
.next()
|
19
|
+
.unwrap_or_else(|| "./ext/himg/examples/assets/github_profile.html".into());
|
20
|
+
println!("Loading {}", path_string);
|
21
|
+
|
22
|
+
// Fetch HTML from path
|
23
|
+
let html = std::fs::read_to_string(&path_string).unwrap();
|
24
|
+
logger.log("Fetched HTML");
|
25
|
+
|
26
|
+
// Configure viewport dimensions
|
27
|
+
let options = Options {
|
28
|
+
image_size: ImageSize {
|
29
|
+
width: 1200,
|
30
|
+
height: 800,
|
31
|
+
hidpi_scale: 1.0,
|
32
|
+
},
|
33
|
+
color_scheme: ColorScheme::Light,
|
34
|
+
allow_net_requests: true, //TODO: Implement using this
|
35
|
+
};
|
36
|
+
|
37
|
+
// Render to Image
|
38
|
+
let base_url = format!("file://{}", path_string.clone());
|
39
|
+
let buffer = html_to_image(&html, Some(base_url), options, &mut logger).await;
|
40
|
+
|
41
|
+
// Determine output path, and open a file at that path.
|
42
|
+
let out_path = compute_filename(&path_string);
|
43
|
+
let mut file = File::create(&out_path).unwrap();
|
44
|
+
|
45
|
+
// Encode buffer as PNG and write it to a file
|
46
|
+
write_png(&mut file, &buffer, options.image_size.scaled_width(), options.image_size.scaled_height());
|
47
|
+
logger.log("Wrote out png");
|
48
|
+
|
49
|
+
logger.log_total_time("\nDone");
|
50
|
+
println!("Written to {}", out_path.display());
|
51
|
+
}
|
52
|
+
|
53
|
+
fn compute_filename(path_string: &str) -> PathBuf {
|
54
|
+
let cargo_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
|
55
|
+
let out_dir = cargo_dir.join("output");
|
56
|
+
|
57
|
+
let base_path = Path::new(path_string).file_stem().unwrap();
|
58
|
+
|
59
|
+
out_dir.join(&base_path).with_extension("png")
|
60
|
+
}
|
61
|
+
|
data/ext/himg/extconf.rb
ADDED
File without changes
|
@@ -0,0 +1,87 @@
|
|
1
|
+
use blitz_dom::net::Resource;
|
2
|
+
use blitz_html::HtmlDocument;
|
3
|
+
use blitz_net::{MpscCallback, Provider};
|
4
|
+
use blitz_renderer_vello::render_to_buffer;
|
5
|
+
use blitz_traits::navigation::DummyNavigationProvider;
|
6
|
+
use blitz_traits::net::SharedProvider;
|
7
|
+
use blitz_traits::{ColorScheme, Viewport};
|
8
|
+
use std::sync::Arc;
|
9
|
+
|
10
|
+
use crate::image_size::ImageSize;
|
11
|
+
use crate::logger::Logger;
|
12
|
+
use crate::options::Options;
|
13
|
+
|
14
|
+
pub async fn html_to_image(
|
15
|
+
html: &str,
|
16
|
+
base_url: Option<String>,
|
17
|
+
options: Options,
|
18
|
+
logger: &mut dyn Logger,
|
19
|
+
) -> Vec<u8> {
|
20
|
+
let (mut recv, callback) = MpscCallback::new();
|
21
|
+
logger.log("Initial config");
|
22
|
+
|
23
|
+
let callback = Arc::new(callback);
|
24
|
+
let net = Arc::new(Provider::new(callback));
|
25
|
+
logger.log("Setup blitz-net Provider");
|
26
|
+
|
27
|
+
let navigation_provider = Arc::new(DummyNavigationProvider);
|
28
|
+
logger.log("Setup dummy navigation provider");
|
29
|
+
|
30
|
+
// Create HtmlDocument
|
31
|
+
let mut document = HtmlDocument::from_html(
|
32
|
+
&html,
|
33
|
+
base_url,
|
34
|
+
Vec::new(),
|
35
|
+
Arc::clone(&net) as SharedProvider<Resource>,
|
36
|
+
None,
|
37
|
+
navigation_provider,
|
38
|
+
);
|
39
|
+
logger.log("Parsed document");
|
40
|
+
|
41
|
+
document.as_mut().set_viewport(Viewport::new(
|
42
|
+
options.image_size.scaled_width(),
|
43
|
+
options.image_size.scaled_height(),
|
44
|
+
options.image_size.hidpi_scale,
|
45
|
+
options.color_scheme,
|
46
|
+
));
|
47
|
+
|
48
|
+
while !net.is_empty() {
|
49
|
+
let Some((_, res)) = recv.recv().await else {
|
50
|
+
break;
|
51
|
+
};
|
52
|
+
document.as_mut().load_resource(res);
|
53
|
+
}
|
54
|
+
|
55
|
+
logger.log("Fetched assets");
|
56
|
+
|
57
|
+
// Compute style, layout, etc for HtmlDocument
|
58
|
+
document.as_mut().resolve();
|
59
|
+
logger.log("Resolved styles and layout");
|
60
|
+
|
61
|
+
// Determine height to render
|
62
|
+
let computed_height = document.as_ref().root_element().final_layout.size.height;
|
63
|
+
let render_height = (computed_height as u32).max(options.image_size.height).min(4000);
|
64
|
+
let render_size = ImageSize {
|
65
|
+
height: render_height,
|
66
|
+
..options.image_size
|
67
|
+
};
|
68
|
+
logger.log("Calculated render dimensions from document");
|
69
|
+
|
70
|
+
println!("Screenshot is ({}x{})",render_size.scaled_width(), render_size.scaled_height());
|
71
|
+
|
72
|
+
// Render document to RGBA buffer
|
73
|
+
let buffer = render_to_buffer(
|
74
|
+
document.as_ref(),
|
75
|
+
Viewport::new(
|
76
|
+
render_size.scaled_width(),
|
77
|
+
render_size.scaled_height(),
|
78
|
+
render_size.hidpi_scale,
|
79
|
+
ColorScheme::Light,
|
80
|
+
),
|
81
|
+
)
|
82
|
+
.await;
|
83
|
+
|
84
|
+
logger.log("Rendered to buffer");
|
85
|
+
|
86
|
+
buffer
|
87
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#[derive(Clone, Copy)]
|
2
|
+
pub struct ImageSize {
|
3
|
+
pub width: u32,
|
4
|
+
pub height: u32,
|
5
|
+
pub hidpi_scale: f32,
|
6
|
+
}
|
7
|
+
|
8
|
+
impl ImageSize {
|
9
|
+
pub fn scaled_width(&self) -> u32 {
|
10
|
+
(self.width as f64 * self.hidpi_scale as f64) as u32
|
11
|
+
}
|
12
|
+
|
13
|
+
pub fn scaled_height(&self) -> u32 {
|
14
|
+
(self.height as f64 * self.hidpi_scale as f64) as u32
|
15
|
+
}
|
16
|
+
|
17
|
+
//pub fn scale(&self, value: u32) -> u32 {
|
18
|
+
// (value as f64 * self.hidpi_scale as f64) as u32
|
19
|
+
//}
|
20
|
+
}
|
data/ext/himg/src/lib.rs
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
pub mod writer;
|
2
|
+
pub mod image_size;
|
3
|
+
pub mod html_to_image;
|
4
|
+
pub mod logger;
|
5
|
+
pub mod options;
|
6
|
+
|
7
|
+
pub use html_to_image::html_to_image;
|
8
|
+
pub use image_size::ImageSize;
|
9
|
+
pub use options::Options;
|
10
|
+
pub use writer::write_png;
|
11
|
+
pub use logger::{Logger, TimedLogger};
|
12
|
+
|
13
|
+
use blitz_traits::{ColorScheme};
|
14
|
+
use magnus::{function, prelude::*, Error, Ruby};
|
15
|
+
|
16
|
+
pub fn render_blocking(html: String) -> Vec<u8> {
|
17
|
+
tokio::runtime::Runtime::new()
|
18
|
+
.unwrap()
|
19
|
+
.block_on(render(html))
|
20
|
+
}
|
21
|
+
|
22
|
+
pub async fn render(html: String) -> Vec<u8> {
|
23
|
+
let mut logger = TimedLogger::init();
|
24
|
+
|
25
|
+
// Configure viewport dimensions
|
26
|
+
let options = Options {
|
27
|
+
image_size: ImageSize {
|
28
|
+
width: 1200, //TODO: pass this in
|
29
|
+
height: 800, //TODO: decide if this will be fixed or dynamic from the document
|
30
|
+
hidpi_scale: 1.0,
|
31
|
+
},
|
32
|
+
color_scheme: ColorScheme::Light,
|
33
|
+
allow_net_requests: true, //TODO: Implement using this
|
34
|
+
};
|
35
|
+
|
36
|
+
// Render to Image
|
37
|
+
//let base_url = format!("file://{}", path_string.clone());
|
38
|
+
let base_url = None;
|
39
|
+
let image_data = html_to_image(&html, base_url, options, &mut logger).await;
|
40
|
+
|
41
|
+
// Determine output path, and open a file at that path.
|
42
|
+
let mut output_buffer: Vec<u8> = Vec::new();
|
43
|
+
|
44
|
+
// Encode buffer as PNG and write it to a file
|
45
|
+
write_png(&mut output_buffer, &image_data, options.image_size.scaled_width(), options.image_size.scaled_height());
|
46
|
+
|
47
|
+
output_buffer
|
48
|
+
}
|
49
|
+
|
50
|
+
#[magnus::init]
|
51
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
52
|
+
let module = ruby.define_module("Himg")?;
|
53
|
+
|
54
|
+
//TODO: Allow optional base_url for resolving linked resources (stylesheets, images, fonts, etc)
|
55
|
+
module.define_singleton_method("render", function!(render_blocking, 1))?;
|
56
|
+
Ok(())
|
57
|
+
}
|
58
|
+
|
59
|
+
#[cfg(test)]
|
60
|
+
mod tests {
|
61
|
+
use rb_sys_test_helpers::ruby_test;
|
62
|
+
use super::hello;
|
63
|
+
|
64
|
+
#[ruby_test]
|
65
|
+
fn test_hello() {
|
66
|
+
assert_eq!("Hello world, from Rust!", hello("world".to_string()));
|
67
|
+
}
|
68
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
use std::time::Instant;
|
2
|
+
|
3
|
+
pub trait Logger {
|
4
|
+
fn log(&mut self, message: &str);
|
5
|
+
}
|
6
|
+
|
7
|
+
pub struct NullLogger;
|
8
|
+
|
9
|
+
impl Logger for NullLogger {
|
10
|
+
fn log(&mut self, _message: &str) {
|
11
|
+
// no-op
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
pub struct TimedLogger {
|
16
|
+
initial_time: Instant,
|
17
|
+
last_time: Instant,
|
18
|
+
}
|
19
|
+
|
20
|
+
impl TimedLogger {
|
21
|
+
pub fn init() -> Self {
|
22
|
+
let now = Instant::now();
|
23
|
+
Self {
|
24
|
+
initial_time: now,
|
25
|
+
last_time: now,
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
pub fn log_total_time(&mut self, message: &str) {
|
30
|
+
let now = Instant::now();
|
31
|
+
let diff = (now - self.initial_time).as_millis();
|
32
|
+
println!("{message} in {diff}ms");
|
33
|
+
|
34
|
+
self.last_time = now;
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
impl Logger for TimedLogger {
|
39
|
+
fn log(&mut self, message: &str) {
|
40
|
+
let now = Instant::now();
|
41
|
+
let diff = (now - self.last_time).as_millis();
|
42
|
+
println!("{message} in {diff}ms");
|
43
|
+
self.last_time = now;
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
use std::io::Write;
|
2
|
+
use png::{Encoder, ColorType, BitDepth, PixelDimensions, Unit};
|
3
|
+
|
4
|
+
const INCHES_PER_METER: f64 = 39.3701;
|
5
|
+
const DEFAULT_DPI: f64 = 144.0;
|
6
|
+
|
7
|
+
pub fn write_png<W: Write>(writer: W, buffer: &[u8], width: u32, height: u32) {
|
8
|
+
let encoder = create_encoder(writer, width, height, DEFAULT_DPI);
|
9
|
+
write_data(encoder, buffer);
|
10
|
+
}
|
11
|
+
|
12
|
+
fn create_encoder<'a, W: Write>(writer: W, width: u32, height: u32, dpi: f64) -> Encoder<'a, W> {
|
13
|
+
let pixels_per_meter = (dpi * INCHES_PER_METER) as u32;
|
14
|
+
|
15
|
+
let mut encoder = Encoder::new(writer, width, height);
|
16
|
+
encoder.set_color(ColorType::Rgba);
|
17
|
+
encoder.set_depth(BitDepth::Eight);
|
18
|
+
encoder.set_pixel_dims(Some(PixelDimensions {
|
19
|
+
xppu: pixels_per_meter,
|
20
|
+
yppu: pixels_per_meter,
|
21
|
+
unit: Unit::Meter,
|
22
|
+
}));
|
23
|
+
|
24
|
+
encoder
|
25
|
+
}
|
26
|
+
|
27
|
+
fn write_data<W: Write>(encoder: Encoder<W>, buffer: &[u8]) {
|
28
|
+
//TODO: Better error handling instead of unwrap
|
29
|
+
let mut writer = encoder.write_header().unwrap();
|
30
|
+
|
31
|
+
writer.write_image_data(buffer).unwrap();
|
32
|
+
writer.finish().unwrap();
|
33
|
+
}
|
data/lib/himg/version.rb
ADDED
data/lib/himg.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "himg/version"
|
4
|
+
|
5
|
+
# Attempt to load a versioned extension based on the Ruby version.
|
6
|
+
# Fall back to loading the non-versioned extension if version-specific loading fails.
|
7
|
+
begin
|
8
|
+
RUBY_VERSION =~ /(\d+\.\d+)/
|
9
|
+
require "#{Regexp.last_match(1)}/himg/himg"
|
10
|
+
rescue LoadError
|
11
|
+
require "himg/himg"
|
12
|
+
end
|
13
|
+
|
14
|
+
# The Hyper Image Generator
|
15
|
+
#
|
16
|
+
# Converts HTML to an Image for a minimal subset of HTML and CSS
|
17
|
+
module Himg
|
18
|
+
#class Error < StandardError; end
|
19
|
+
end
|
data/sig/himg.rbs
ADDED
data/test.html.rbd
ADDED
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: himg
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Edwards-Jones
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: rb_sys
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0.9'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0.9'
|
26
|
+
description: Generates images from a minimal subset of HTML and CSS
|
27
|
+
email:
|
28
|
+
- git@jamedjo.co.uk
|
29
|
+
executables: []
|
30
|
+
extensions:
|
31
|
+
- ext/himg/extconf.rb
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".rspec"
|
35
|
+
- ".ruby-version"
|
36
|
+
- ".standard.yml"
|
37
|
+
- CHANGELOG.md
|
38
|
+
- Cargo.lock
|
39
|
+
- Cargo.toml
|
40
|
+
- LICENSE.txt
|
41
|
+
- README.md
|
42
|
+
- Rakefile
|
43
|
+
- Steepfile
|
44
|
+
- ext/himg/Cargo.lock
|
45
|
+
- ext/himg/Cargo.toml
|
46
|
+
- ext/himg/build.rs
|
47
|
+
- ext/himg/examples/assets/github_profile.html
|
48
|
+
- ext/himg/examples/file.rs
|
49
|
+
- ext/himg/extconf.rb
|
50
|
+
- ext/himg/output/.gitkeep
|
51
|
+
- ext/himg/src/html_to_image.rs
|
52
|
+
- ext/himg/src/image_size.rs
|
53
|
+
- ext/himg/src/lib.rs
|
54
|
+
- ext/himg/src/logger.rs
|
55
|
+
- ext/himg/src/options.rs
|
56
|
+
- ext/himg/src/writer.rs
|
57
|
+
- lib/himg.rb
|
58
|
+
- lib/himg/version.rb
|
59
|
+
- sig/himg.rbs
|
60
|
+
- test.html.rbd
|
61
|
+
homepage: https://github.com/Jamedjo/Himg
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata:
|
65
|
+
homepage_uri: https://github.com/Jamedjo/Himg
|
66
|
+
source_code_uri: https://github.com/Jamedjo/Himg
|
67
|
+
changelog_uri: https://github.com/Jamedjo/Himg/blob/CHANGELOG.md
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.1.0
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubygems_version: 3.6.7
|
83
|
+
specification_version: 4
|
84
|
+
summary: The Hyper Image Generator
|
85
|
+
test_files: []
|