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.
@@ -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,5 @@
1
+ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
2
+ let _ = rb_sys_env::activate()?;
3
+
4
+ Ok(())
5
+ }
@@ -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="{&quot;event_type&quot;:&quot;user_profile.click&quot;,&quot;payload&quot;:{&quot;profile_user_id&quot;:1007307,&quot;target&quot;:&quot;PINNED_REPO&quot;,&quot;user_id&quot;:1007307,&quot;originating_url&quot;:&quot;https://github.com/nicoburns&quot;}}" 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
+
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+ require "rb_sys/mkmf"
5
+
6
+ create_rust_makefile("himg/himg")
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
+ }
@@ -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,10 @@
1
+ use crate::image_size::ImageSize;
2
+
3
+ use blitz_traits::ColorScheme;
4
+
5
+ #[derive(Clone, Copy)]
6
+ pub struct Options {
7
+ pub image_size: ImageSize,
8
+ pub color_scheme: ColorScheme,
9
+ pub allow_net_requests: bool,
10
+ }
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Himg
4
+ VERSION = "0.0.1"
5
+ end
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
@@ -0,0 +1,8 @@
1
+ module Himg
2
+ VERSION: String
3
+
4
+ class Renderer
5
+ def initialize: (Integer width, Integer height) -> void
6
+ def render: (String html) -> String
7
+ end
8
+ end
data/test.html.rbd ADDED
@@ -0,0 +1,5 @@
1
+ <div style="background-color: red; width: '100%'; height: '100%'>
2
+
3
+ <h1>Hello World</h1>
4
+ __From_Rust_Ruby__
5
+ </div>
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: []