himg 0.0.3 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 286080c98560f491c524867c0986edd71e7e14bc912a50a2a72d627d4cb22e34
4
- data.tar.gz: 435a08c13f0211d694ca477d4ce8a7afba24c686d088a7b7831c0397177d127c
3
+ metadata.gz: 1bd4c145135ae494ac36cefa1aade53d31d5314e54ce582126c76484115815f7
4
+ data.tar.gz: f631474fc095ea589af509e8c27e0386efb102868b225bad5be708d10e0b682a
5
5
  SHA512:
6
- metadata.gz: d5ca027f7d92741dc71b872a5308a05228c9e7c7743ebcfd976175f3c4a5a0482d3831e0d3f664aa1a7870353e8d2d482c1e545b0fa558d802ca3131282323bd
7
- data.tar.gz: e4a9e936a5d96a1574c9af213912bed55de8e25af94775007397a70a7bdebc9f5b41519fc35668738c0929e9f50821b18f1c58d0897f84443334fcba15b4973c
6
+ metadata.gz: c666c9444a47522f1843e34c3c065ee06e15d2995a5cbe0a537ab17acb96bfdb08414735c5c468ea47609169739f37bda38c4e74a745f12b643c45a1eaa1ded8
7
+ data.tar.gz: e629137b2de381bf1ed7f5cf5d6caa694372172056e9f1c7c80d6d879011641162ecf486863e51147349133ef1a2e189c13b13406d19bbd0971c9f3d9926d810
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.5] - 2025-04-22
4
+
5
+ - Can configure `render himg: ""` with options including `width:`, `height:`,
6
+ `truncate:` and `verbose:`.
7
+ - Can use `himg_config` helper methods at controller level and action level
8
+ to set `@_himg_config`, which can then be passed to the renderer with
9
+ `render himg: "<!DOCTYPE html>", config: himg_config` as an alternative to
10
+ specifying config options individually to `ActionController::Rendering#render`.
11
+ - `himg_config` helpers can also be used to control the configuration of
12
+ template based default render, when not calling render manually within the
13
+ controller. This works because the template handler can access `@_himg_config`.
14
+ - Can use string width / height with Himg.render
15
+ - Disable detailed log timings by default. Can re-enable with verbose: true.
16
+ There is still some logging from blitz-net#fetch for http requests and for
17
+ blitz-html::DocumentHtmlParser#finish on unexpected tokens.
18
+
19
+ ## [0.0.4] - 2025-04-22
20
+
21
+ - Allow `width`, `height` and `truncate` to be passed to the render function.
22
+
3
23
  ## [0.0.3] - 2025-04-22
4
24
 
5
25
  - Ensure that when render height is expanded to fit the content that we update
data/Cargo.lock CHANGED
@@ -1065,7 +1065,7 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
1065
1065
 
1066
1066
  [[package]]
1067
1067
  name = "himg"
1068
- version = "0.0.3"
1068
+ version = "0.0.5"
1069
1069
  dependencies = [
1070
1070
  "blitz-dom",
1071
1071
  "blitz-html",
data/README.md CHANGED
@@ -16,7 +16,6 @@ In Rails this will mean you can process user.himg.erb to display an image includ
16
16
  4. Network requests can be made: don't use this library with untrusted inputs.
17
17
  5. file:// URLs are resolved: this could expose files on your computer.
18
18
  6. Native extensions are not yet being published for different os/arch
19
- 7. Verbose logging is hardcoded
20
19
 
21
20
  ## Installation
22
21
 
@@ -37,7 +36,13 @@ gem install himg
37
36
  ### Ruby
38
37
 
39
38
  ```ruby
40
- png = Himg.render("<html bgcolor='blue'></html>")
39
+ png = Himg.render("<!DOCTYPE html><body style='background:blue;'></body>")
40
+ File.open("blue.png", "wb"){|f| f.write(png) }
41
+ ```
42
+
43
+ ```ruby
44
+ png = Himg.render("<!DOCTYPE html><h1>Snapshot</h1>", width: 89, height: 5, truncate: false)
45
+ File.open("dynamic_height.png", "wb"){|f| f.write(png) }
41
46
  ```
42
47
 
43
48
  ### Rails
@@ -58,13 +63,46 @@ Once you've added a view template for your resource, you can use it to generate
58
63
  <meta property="og:image" content="<%= user_url(@user.username, format: :png) %>" />
59
64
  ```
60
65
 
61
- ### Advanced Rails Usage
66
+ ### Configuration Options
67
+
68
+ Options: `width`, `height`, `verbose`, `truncate`.
69
+
70
+ ### Passing options to a Rails view template
71
+
72
+ Options can be set at a controller level using the `himg_config` helper method:
73
+ ```ruby
74
+ class UsersController < ActionController::Base
75
+ himg_config(verbose: true)
76
+ end
77
+ ```
78
+
79
+ These can be overridden at a view level:
80
+ ```ruby
81
+ class UsersController < ActionController::Base
82
+ def show
83
+ himg_config(width: params[:w]) if params[:w]
84
+
85
+ @user = User.new
86
+ end
87
+ ```
62
88
 
63
- A :himg template handler is registered and will be called by rails' `default_render` method automatically when the corresponding view is found. This can be `show.himg` for a static image, or `show.himg.erb` to use variables from the controller.
89
+ ### Rails manual render
64
90
 
65
91
  If you prefer you could also use `render himg: "<div>My Data</div>"` instead, but should be careful with untrusted input if constructing HTML manually.
66
92
 
67
- To be explicit in the controler you can also use `respond_to` style:
93
+ Options can then be passed directly to the manual render:
94
+ ```ruby
95
+ render himg: '<!DOCTYPE html>', truncate: false
96
+ ```
97
+
98
+ Alternatively you can pass in options which have been set with `himg_config`:
99
+ ```ruby
100
+ render himg: '<!DOCTYPE html>', config: himg_config
101
+ ```
102
+
103
+ ### Rails `respond_to`
104
+
105
+ To be explicit in the controller you can also use `respond_to` style:
68
106
 
69
107
  ```ruby
70
108
  respond_to do |format|
@@ -73,6 +111,7 @@ respond_to do |format|
73
111
  end
74
112
  ```
75
113
 
114
+ You can also use this combined with a manual render:
76
115
  ```ruby
77
116
  respond_to do |format|
78
117
  format.html
@@ -81,6 +120,16 @@ respond_to do |format|
81
120
  end
82
121
  ```
83
122
 
123
+ ### How it works
124
+
125
+ No browser, just basics!
126
+
127
+ Himg calls through to the amazing blitz library, which uses Stylo to parse the CSS, servo/html5ever to parse the HTML, fetches network resources, builds a scene graph and hands over to vello to render an image.
128
+
129
+ Interaction between Ruby & Rust is done with the help of `magnus`, `rb_sys` and lots of glue code from the `oxidize-rb` team.
130
+
131
+ To play nicely with Rails a template handler is registered, which Rails' `default_render` method automatically calls when the corresponding view is found. This can be `show.himg` for a static image, or `show.himg.erb` to use variables from the controller. Additionally a Renderer is available with `render himg: 'content'` in case a view template is not needed.
132
+
84
133
  ### Run directly from the command line to output an image
85
134
 
86
135
  ```bash
data/Rakefile CHANGED
@@ -20,7 +20,7 @@ RbSys::ExtensionTask.new("himg", GEMSPEC) do |ext|
20
20
  end
21
21
 
22
22
  require "bump/tasks"
23
- Bump.replace_in_default = %w[ext/himg/Cargo.toml]
23
+ Bump.replace_in_default = %w[Cargo.lock ext/himg/Cargo.toml]
24
24
  Bump.changelog = :editor
25
25
 
26
26
  task default: %i[compile rubocop]
data/ext/himg/Cargo.toml CHANGED
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "himg"
3
3
  description = "ruby bindings to expose a blitz html->png pipeline"
4
- version = "0.0.3"
4
+ version = "0.0.5"
5
5
  edition = "2024"
6
6
  authors = ["James Edwards-Jones <git@jamedjo.co.uk>"]
7
7
  license = "MIT"
@@ -21,7 +21,7 @@ async fn main() {
21
21
 
22
22
  // Fetch HTML from path
23
23
  let html = std::fs::read_to_string(&path_string).unwrap();
24
- logger.log("Fetched HTML");
24
+ logger.log("Read HTML");
25
25
 
26
26
  // Configure viewport dimensions
27
27
  let options = Options {
@@ -30,20 +30,22 @@ async fn main() {
30
30
  height: 800,
31
31
  hidpi_scale: 1.0,
32
32
  },
33
+ truncate: false,
34
+ verbose: true,
33
35
  color_scheme: ColorScheme::Light,
34
36
  allow_net_requests: true, //TODO: Implement using this
35
37
  };
36
38
 
37
39
  // Render to Image
38
40
  let base_url = format!("file://{}", path_string.clone());
39
- let buffer = html_to_image(&html, Some(base_url), options, &mut logger).await;
41
+ let render_output = html_to_image(&html, Some(base_url), options, &mut logger).await;
40
42
 
41
43
  // Determine output path, and open a file at that path.
42
44
  let out_path = compute_filename(&path_string);
43
45
  let mut file = File::create(&out_path).unwrap();
44
46
 
45
47
  // 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()).unwrap();
48
+ write_png(&mut file, &render_output.buffer, render_output.image_size.scaled_width(), render_output.image_size.scaled_height()).unwrap();
47
49
  logger.log("Wrote out png");
48
50
 
49
51
  logger.log_total_time("\nDone");
@@ -64,15 +64,21 @@ pub async fn html_to_image(
64
64
  logger.log("Resolved styles and layout");
65
65
 
66
66
  // Determine height to render
67
- let computed_height = document.as_ref().root_element().final_layout.size.height;
68
- let render_height = (computed_height as u32).max(options.image_size.height).min(4000);
69
- let render_size = ImageSize {
70
- height: render_height,
71
- ..options.image_size
67
+ let render_size = if options.truncate {
68
+ options.image_size
69
+ } else {
70
+ let computed_height = document.as_ref().root_element().final_layout.size.height;
71
+ let render_height = (computed_height as u32).max(options.image_size.height).min(10_000);
72
+ ImageSize {
73
+ height: render_height,
74
+ ..options.image_size
75
+ }
72
76
  };
73
77
  logger.log("Calculated render dimensions from document");
74
78
 
75
- println!("Screenshot is ({}x{})",render_size.scaled_width(), render_size.scaled_height());
79
+ if options.verbose {
80
+ println!("Screenshot is ({}x{})",render_size.scaled_width(), render_size.scaled_height());
81
+ }
76
82
 
77
83
  // Render document to RGBA buffer
78
84
  let buffer = render_to_buffer(
data/ext/himg/src/lib.rs CHANGED
@@ -1,57 +1,49 @@
1
- pub mod writer;
2
- pub mod image_size;
1
+ pub mod renderer;
3
2
  pub mod html_to_image;
4
- pub mod logger;
3
+ pub mod image_size;
5
4
  pub mod options;
5
+ pub mod writer;
6
+ pub mod logger;
6
7
 
7
- pub use html_to_image::html_to_image;
8
+ pub use renderer::render_blocking;
8
9
  pub use image_size::ImageSize;
9
10
  pub use options::Options;
11
+ pub use html_to_image::html_to_image;
10
12
  pub use writer::write_png;
11
- pub use logger::{Logger, TimedLogger};
12
-
13
- use blitz_traits::{ColorScheme};
14
- use magnus::{function, prelude::*, ExceptionClass, Error, Ruby};
15
13
 
16
- pub fn render_blocking(html: String) -> Result<Vec<u8>, std::io::Error> {
17
- let runtime = tokio::runtime::Runtime::new()?;
14
+ use blitz_traits::ColorScheme;
15
+ use magnus::{function, prelude::*, ExceptionClass, Error, Ruby, RString, RHash};
18
16
 
19
- runtime.block_on(render(html))
20
- }
21
-
22
- // render_to_bytes, render_to_string, render_to_file, render_to_io
23
- pub async fn render(html: String) -> Result<Vec<u8>, std::io::Error> {
24
- let mut logger = TimedLogger::init();
25
-
26
- // Configure viewport dimensions
27
- let options = Options {
28
- image_size: ImageSize {
29
- width: 720, //TODO: pass this in
30
- height: 405, //TODO: decide if this will be fixed or dynamic from the document
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 base_url = None;
40
- let render_output = html_to_image(&html, base_url, options, &mut logger).await;
41
-
42
- // Determine output path, and open a file at that path.
43
- let mut output_buffer: Vec<u8> = Vec::new();
44
-
45
- // Encode buffer as PNG and write it to a file
46
- write_png(&mut output_buffer, &render_output.buffer, render_output.image_size.scaled_width(), render_output.image_size.scaled_height())?;
17
+ impl Options {
18
+ fn get_option<V: magnus::TryConvert + magnus::IntoValue>(optional_hash: Option<RHash>, key: &str, default: V) -> Result<V, Error> {
19
+ match optional_hash {
20
+ Some(hash) => hash.lookup2::<&str, V, V>(key, default),
21
+ None => Ok(default),
22
+ }
23
+ }
47
24
 
48
- Ok(output_buffer)
25
+ pub fn from_ruby(hash: Option<RHash>) -> Result<Self, Error> {
26
+ let options = Options {
27
+ image_size: ImageSize {
28
+ width: Self::get_option(hash, "width", 720)?,
29
+ height: Self::get_option(hash, "height", 405)?,
30
+ hidpi_scale: 1.0,
31
+ },
32
+ truncate: Self::get_option(hash, "truncate", true)?,
33
+ verbose: Self::get_option(hash, "verbose", false)?,
34
+ color_scheme: ColorScheme::Light,
35
+ allow_net_requests: true, //TODO: Implement using this
36
+ };
37
+
38
+ Ok(options)
39
+ }
49
40
  }
50
41
 
51
- pub fn render_blocking_rb(ruby: &Ruby, html: String) -> Result<magnus::RString, magnus::Error> {
42
+ pub fn render_blocking_rb(ruby: &Ruby, html: String, options: Option<RHash>) -> Result<RString, Error> {
43
+ let options = Options::from_ruby(options)?;
52
44
  let exception_class = ExceptionClass::from_value(magnus::eval("Himg::Error").unwrap()).unwrap();
53
45
 
54
- match render_blocking(html) {
46
+ match render_blocking(html, options) {
55
47
  Ok(data) => Ok(ruby.str_from_slice(&data)),
56
48
  Err(e) => Err(Error::new(exception_class, format!("{}", e))),
57
49
  }
@@ -62,7 +54,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
62
54
  let module = ruby.define_module("Himg")?;
63
55
 
64
56
  //TODO: Allow optional base_url for resolving linked resources (stylesheets, images, fonts, etc)
65
- module.define_singleton_method("render", function!(render_blocking_rb, 1))?;
57
+ module.define_singleton_method("render_to_string", function!(render_blocking_rb, 2))?;
66
58
 
67
59
  Ok(())
68
60
  }
@@ -7,4 +7,6 @@ pub struct Options {
7
7
  pub image_size: ImageSize,
8
8
  pub color_scheme: ColorScheme,
9
9
  pub allow_net_requests: bool,
10
+ pub truncate: bool,
11
+ pub verbose: bool,
10
12
  }
@@ -0,0 +1,32 @@
1
+ use crate::html_to_image::html_to_image;
2
+ use crate::options::Options;
3
+ use crate::writer::write_png;
4
+ use crate::logger::{Logger, NullLogger, TimedLogger};
5
+
6
+ pub fn render_blocking(html: String, options: Options) -> Result<Vec<u8>, std::io::Error> {
7
+ let runtime = tokio::runtime::Runtime::new()?;
8
+
9
+ runtime.block_on(render(html, options))
10
+ }
11
+
12
+ // render_to_bytes, render_to_string, render_to_file, render_to_io
13
+ pub async fn render(html: String, options: Options) -> Result<Vec<u8>, std::io::Error> {
14
+ let mut logger: Box<dyn Logger> = if options.verbose {
15
+ Box::new(TimedLogger::init())
16
+ } else {
17
+ Box::new(NullLogger{})
18
+ };
19
+
20
+ // Render to Image
21
+ //let base_url = format!("file://{}", path_string.clone());
22
+ let base_url = None;
23
+ let render_output = html_to_image(&html, base_url, options, &mut *logger).await;
24
+
25
+ // Determine output path, and open a file at that path.
26
+ let mut output_buffer: Vec<u8> = Vec::new();
27
+
28
+ // Encode buffer as PNG and write it to a file
29
+ write_png(&mut output_buffer, &render_output.buffer, render_output.image_size.scaled_width(), render_output.image_size.scaled_height())?;
30
+
31
+ Ok(output_buffer)
32
+ }
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- himg (0.0.2)
4
+ himg (0.0.4)
5
5
  rb_sys (~> 0.9)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- himg (0.0.2)
4
+ himg (0.0.4)
5
5
  rb_sys (~> 0.9)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- himg (0.0.2)
4
+ himg (0.0.4)
5
5
  rb_sys (~> 0.9)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- himg (0.0.2)
4
+ himg (0.0.4)
5
5
  rb_sys (~> 0.9)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- himg (0.0.2)
4
+ himg (0.0.4)
5
5
  rb_sys (~> 0.9)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- himg (0.0.2)
4
+ himg (0.0.4)
5
5
  rb_sys (~> 0.9)
6
6
 
7
7
  GEM
@@ -0,0 +1,31 @@
1
+ module Himg
2
+ class Railtie
3
+ module ControllerConfig
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_action :_apply_himg_config
8
+ end
9
+
10
+ class_methods do
11
+ def himg_config(options = {})
12
+ @_himg_global_config ||= {}
13
+ @_himg_global_config.merge!(options)
14
+ end
15
+
16
+ def _himg_global_config
17
+ @_himg_global_config&.dup || {}
18
+ end
19
+ end
20
+
21
+ def himg_config(options = {})
22
+ @_himg_config.merge!(options)
23
+ @_himg_config
24
+ end
25
+
26
+ def _apply_himg_config
27
+ @_himg_config = self.class._himg_global_config
28
+ end
29
+ end
30
+ end
31
+ end
@@ -4,7 +4,7 @@ module Himg
4
4
  class TemplateHandler
5
5
  def self.call(_template, source)
6
6
  <<-CODE
7
- Himg.render(#{source.inspect})
7
+ Himg.render(#{source.inspect}, **@_himg_config)
8
8
  CODE
9
9
  end
10
10
  end
@@ -21,7 +21,7 @@ module Himg
21
21
  output = begin
22
22
  #{preprocessed_view_code}
23
23
  end
24
- Himg.render(output)
24
+ Himg.render(output, **@_himg_config)
25
25
  CODE
26
26
  end
27
27
  end
data/lib/himg/railtie.rb CHANGED
@@ -29,10 +29,21 @@ module Himg
29
29
  end
30
30
 
31
31
  initializer "himg.controller_renderer" do
32
- ActionController::Renderers.add :himg do |obj, _options|
33
- png_data = Himg.render(obj)
32
+ ActionController::Renderers.add :himg do |obj, options = {}|
33
+ configured_options = options[:config] || {}
34
+ direct_options = options.symbolize_keys.slice(*Himg::RENDER_OPTIONS)
35
+ merged_options = configured_options.merge(direct_options)
36
+
37
+ png_data = Himg.render(obj, **merged_options)
34
38
  send_data png_data, type: "image/png", disposition: "inline"
35
39
  end
36
40
  end
41
+
42
+ initializer 'himg.controller_config' do
43
+ require "himg/railtie/controller_config"
44
+ ActiveSupport.on_load(:action_controller) do
45
+ include Himg::Railtie::ControllerConfig
46
+ end
47
+ end
37
48
  end
38
49
  end
data/lib/himg/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Himg
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.5"
5
5
  end
data/lib/himg.rb CHANGED
@@ -16,5 +16,10 @@ end
16
16
  #
17
17
  # Converts HTML to an Image for a minimal subset of HTML and CSS
18
18
  module Himg
19
+ RENDER_OPTIONS = %i[width height truncate verbose].freeze
19
20
  class Error < StandardError; end
21
+
22
+ def self.render(html, width: 720, height: 405, truncate: true, verbose: false)
23
+ render_to_string(html, "width" => width.to_i, "height" => height.to_i, "truncate" => truncate, "verbose" => verbose)
24
+ end
20
25
  end
data/sig/himg.rbs CHANGED
@@ -1,8 +1,16 @@
1
1
  module Himg
2
2
  VERSION: String
3
3
 
4
- class Renderer
5
- def initialize: (Integer width, Integer height) -> void
6
- def render: (String html) -> String
4
+ class Error < StandardError
7
5
  end
6
+
7
+ def self.render: (
8
+ String html,
9
+ ?width: Integer,
10
+ ?height: Integer,
11
+ ?truncate: bool,
12
+ ?verbose: bool
13
+ ) -> String
14
+
15
+ def self.render_to_string: (String html, Hash[string, untyped] options) -> String
8
16
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: himg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Edwards-Jones
@@ -180,6 +180,7 @@ files:
180
180
  - ext/himg/src/lib.rs
181
181
  - ext/himg/src/logger.rs
182
182
  - ext/himg/src/options.rs
183
+ - ext/himg/src/renderer.rs
183
184
  - ext/himg/src/writer.rs
184
185
  - gemfiles/plain_ruby.gemfile
185
186
  - gemfiles/plain_ruby.gemfile.lock
@@ -195,6 +196,7 @@ files:
195
196
  - gemfiles/rails_8.gemfile.lock
196
197
  - lib/himg.rb
197
198
  - lib/himg/railtie.rb
199
+ - lib/himg/railtie/controller_config.rb
198
200
  - lib/himg/railtie/template_handler.rb
199
201
  - lib/himg/version.rb
200
202
  - sig/himg.rbs