himg 0.0.4 → 0.0.6
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 +4 -4
- data/CHANGELOG.md +23 -0
- data/Cargo.lock +548 -1047
- data/LICENSE-APACHE.txt +203 -0
- data/README.md +93 -39
- data/ext/himg/Cargo.toml +10 -7
- data/ext/himg/examples/assets/github_profile_offline.html +86 -0
- data/ext/himg/examples/file.rs +9 -8
- data/ext/himg/src/html_to_image.rs +34 -36
- data/ext/himg/src/image_size.rs +1 -1
- data/ext/himg/src/lib.rs +19 -15
- data/ext/himg/src/net_fetcher.rs +139 -0
- data/ext/himg/src/options.rs +22 -3
- data/ext/himg/src/renderer.rs +7 -5
- data/gemfiles/plain_ruby.gemfile.lock +1 -1
- data/gemfiles/rails_6.gemfile.lock +1 -1
- data/gemfiles/rails_7_0.gemfile.lock +1 -1
- data/gemfiles/rails_7_1.gemfile.lock +1 -1
- data/gemfiles/rails_7_2.gemfile.lock +1 -1
- data/gemfiles/rails_8.gemfile.lock +1 -1
- data/lib/himg/base_url.rb +21 -0
- data/lib/himg/cli.rb +64 -0
- data/lib/himg/railtie/controller_config.rb +31 -0
- data/lib/himg/railtie/template_handler.rb +2 -2
- data/lib/himg/railtie.rb +13 -2
- data/lib/himg/version.rb +1 -1
- data/lib/himg.rb +4 -2
- data/readme_hero.svg +54 -0
- data/sig/himg.rbs +11 -3
- metadata +9 -2
- /data/{LICENSE.txt → LICENSE-MIT.txt} +0 -0
@@ -1,15 +1,14 @@
|
|
1
|
-
use blitz_dom::net::Resource;
|
2
1
|
use blitz_html::HtmlDocument;
|
3
|
-
use
|
4
|
-
use
|
5
|
-
use
|
6
|
-
use
|
7
|
-
use blitz_traits::{
|
8
|
-
use std::sync::Arc;
|
2
|
+
use blitz_dom::DocumentConfig;
|
3
|
+
use anyrender_vello_cpu::VelloCpuImageRenderer;
|
4
|
+
use anyrender::render_to_buffer;
|
5
|
+
use blitz_paint::paint_scene;
|
6
|
+
use blitz_traits::shell::{Viewport};
|
9
7
|
|
10
8
|
use crate::image_size::ImageSize;
|
11
9
|
use crate::logger::Logger;
|
12
10
|
use crate::options::Options;
|
11
|
+
use crate::net_fetcher::NetFetcher;
|
13
12
|
|
14
13
|
pub struct RenderOutput {
|
15
14
|
pub buffer: Vec<u8>,
|
@@ -18,47 +17,43 @@ pub struct RenderOutput {
|
|
18
17
|
|
19
18
|
pub async fn html_to_image(
|
20
19
|
html: &str,
|
21
|
-
base_url: Option<String>,
|
22
20
|
options: Options,
|
23
21
|
logger: &mut dyn Logger,
|
24
22
|
) -> RenderOutput {
|
25
|
-
let
|
26
|
-
|
23
|
+
let mut net_fetcher = if options.disable_fetch {
|
24
|
+
logger.log("Disabled fetching resources");
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
None
|
27
|
+
} else {
|
28
|
+
let fetcher = NetFetcher::new();
|
29
|
+
logger.log("Setup remote resource fetcher");
|
31
30
|
|
32
|
-
|
33
|
-
|
31
|
+
Some(fetcher)
|
32
|
+
};
|
34
33
|
|
35
34
|
// Create HtmlDocument
|
36
35
|
let mut document = HtmlDocument::from_html(
|
37
36
|
&html,
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
37
|
+
DocumentConfig {
|
38
|
+
base_url: options.base_url,
|
39
|
+
net_provider: net_fetcher.as_ref().map(|fetcher| fetcher.get_provider() as _),
|
40
|
+
..Default::default()
|
41
|
+
},
|
43
42
|
);
|
44
43
|
logger.log("Parsed document");
|
45
44
|
|
46
45
|
document.as_mut().set_viewport(Viewport::new(
|
47
46
|
options.image_size.scaled_width(),
|
48
47
|
options.image_size.scaled_height(),
|
49
|
-
options.image_size.hidpi_scale,
|
48
|
+
options.image_size.hidpi_scale as f32,
|
50
49
|
options.color_scheme,
|
51
50
|
));
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
};
|
57
|
-
document.as_mut().load_resource(res);
|
52
|
+
if let Some(ref mut net_fetcher) = net_fetcher {
|
53
|
+
net_fetcher.fetch_resources(&mut document).await;
|
54
|
+
logger.log("Fetched assets");
|
58
55
|
}
|
59
56
|
|
60
|
-
logger.log("Fetched assets");
|
61
|
-
|
62
57
|
// Compute style, layout, etc for HtmlDocument
|
63
58
|
document.as_mut().resolve();
|
64
59
|
logger.log("Resolved styles and layout");
|
@@ -76,19 +71,22 @@ pub async fn html_to_image(
|
|
76
71
|
};
|
77
72
|
logger.log("Calculated render dimensions from document");
|
78
73
|
|
79
|
-
|
74
|
+
if options.verbose {
|
75
|
+
println!("Screenshot is ({}x{})",render_size.scaled_width(), render_size.scaled_height());
|
76
|
+
}
|
80
77
|
|
81
78
|
// Render document to RGBA buffer
|
82
|
-
let buffer = render_to_buffer(
|
83
|
-
|
84
|
-
|
79
|
+
let buffer = render_to_buffer::<VelloCpuImageRenderer, _>(
|
80
|
+
|scene| paint_scene(
|
81
|
+
scene,
|
82
|
+
document.as_ref(),
|
83
|
+
render_size.hidpi_scale,
|
85
84
|
render_size.scaled_width(),
|
86
85
|
render_size.scaled_height(),
|
87
|
-
render_size.hidpi_scale,
|
88
|
-
ColorScheme::Light,
|
89
86
|
),
|
90
|
-
|
91
|
-
|
87
|
+
render_size.scaled_width(),
|
88
|
+
render_size.scaled_height(),
|
89
|
+
);
|
92
90
|
|
93
91
|
logger.log("Rendered to buffer");
|
94
92
|
|
data/ext/himg/src/image_size.rs
CHANGED
data/ext/himg/src/lib.rs
CHANGED
@@ -4,32 +4,37 @@ pub mod image_size;
|
|
4
4
|
pub mod options;
|
5
5
|
pub mod writer;
|
6
6
|
pub mod logger;
|
7
|
+
pub mod net_fetcher;
|
7
8
|
|
8
9
|
pub use renderer::render_blocking;
|
10
|
+
pub use image_size::ImageSize;
|
11
|
+
pub use options::Options;
|
12
|
+
pub use html_to_image::html_to_image;
|
13
|
+
pub use writer::write_png;
|
9
14
|
|
10
|
-
use
|
11
|
-
use crate::options::Options;
|
12
|
-
use blitz_traits::ColorScheme;
|
15
|
+
use blitz_traits::shell::ColorScheme;
|
13
16
|
use magnus::{function, prelude::*, ExceptionClass, Error, Ruby, RString, RHash};
|
14
17
|
|
15
18
|
impl Options {
|
16
|
-
fn get_option<V: magnus::TryConvert + magnus::IntoValue>(optional_hash: Option<RHash>, key: &str, default: V) -> Result<V, Error> {
|
17
|
-
match optional_hash {
|
18
|
-
Some(hash) => hash.lookup2::<&str, V, V>(key, default),
|
19
|
-
None => Ok(default),
|
20
|
-
}
|
21
|
-
}
|
22
|
-
|
23
19
|
pub fn from_ruby(hash: Option<RHash>) -> Result<Self, Error> {
|
20
|
+
let defaults = Options::default();
|
21
|
+
|
22
|
+
let hash = match hash {
|
23
|
+
None => return Ok(defaults),
|
24
|
+
Some(r) => r,
|
25
|
+
};
|
26
|
+
|
24
27
|
let options = Options {
|
25
28
|
image_size: ImageSize {
|
26
|
-
width:
|
27
|
-
height:
|
29
|
+
width: hash.lookup2("width", 720)?,
|
30
|
+
height: hash.lookup2("height", 405)?,
|
28
31
|
hidpi_scale: 1.0,
|
29
32
|
},
|
30
|
-
truncate:
|
33
|
+
truncate: hash.lookup2("truncate", true)?,
|
34
|
+
verbose: hash.lookup2("verbose", false)?,
|
35
|
+
base_url: hash.lookup("base_url")?,
|
36
|
+
disable_fetch: hash.lookup2("disable_fetch", false)?,
|
31
37
|
color_scheme: ColorScheme::Light,
|
32
|
-
allow_net_requests: true, //TODO: Implement using this
|
33
38
|
};
|
34
39
|
|
35
40
|
Ok(options)
|
@@ -50,7 +55,6 @@ pub fn render_blocking_rb(ruby: &Ruby, html: String, options: Option<RHash>) ->
|
|
50
55
|
fn init(ruby: &Ruby) -> Result<(), Error> {
|
51
56
|
let module = ruby.define_module("Himg")?;
|
52
57
|
|
53
|
-
//TODO: Allow optional base_url for resolving linked resources (stylesheets, images, fonts, etc)
|
54
58
|
module.define_singleton_method("render_to_string", function!(render_blocking_rb, 2))?;
|
55
59
|
|
56
60
|
Ok(())
|
@@ -0,0 +1,139 @@
|
|
1
|
+
use blitz_html::HtmlDocument;
|
2
|
+
use blitz_net::Provider;
|
3
|
+
use blitz_dom::net::Resource;
|
4
|
+
use blitz_traits::net::{NetCallback, NetProvider, BoxedHandler, Request};
|
5
|
+
use std::sync::Arc;
|
6
|
+
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel};
|
7
|
+
|
8
|
+
#[derive(Clone)]
|
9
|
+
struct PendingCount {
|
10
|
+
count: Arc<std::sync::atomic::AtomicUsize>,
|
11
|
+
}
|
12
|
+
|
13
|
+
impl PendingCount {
|
14
|
+
fn new() -> Self {
|
15
|
+
Self {
|
16
|
+
count: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
fn current(&self) -> usize {
|
21
|
+
self.count.load(std::sync::atomic::Ordering::SeqCst)
|
22
|
+
}
|
23
|
+
|
24
|
+
fn increment(&self) {
|
25
|
+
self.count.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
26
|
+
}
|
27
|
+
|
28
|
+
fn decrement(&self) {
|
29
|
+
self.count.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
|
30
|
+
}
|
31
|
+
|
32
|
+
fn is_empty(&self) -> bool {
|
33
|
+
self.current() == 0
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
pub struct ErrorHandlingCallback<T>(UnboundedSender<(usize, Result<T, Option<String>>)>);
|
38
|
+
impl<T> ErrorHandlingCallback<T> {
|
39
|
+
pub fn new() -> (UnboundedReceiver<(usize, Result<T, Option<String>>)>, Self) {
|
40
|
+
let (send, recv) = unbounded_channel();
|
41
|
+
(recv, Self(send))
|
42
|
+
}
|
43
|
+
}
|
44
|
+
impl<T: Send + Sync + 'static> NetCallback<T> for ErrorHandlingCallback<T> {
|
45
|
+
fn call(&self, doc_id: usize, result: Result<T, Option<String>>) {
|
46
|
+
let _ = self.0.send((doc_id, result));
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
pub struct ErrorHandlingProvider<D> {
|
51
|
+
inner: Arc<Provider<D>>,
|
52
|
+
callback: Arc<dyn NetCallback<D>>,
|
53
|
+
pending_requests: PendingCount,
|
54
|
+
}
|
55
|
+
|
56
|
+
impl<D: Send + Sync + 'static> ErrorHandlingProvider<D> {
|
57
|
+
pub fn new(callback: Arc<dyn NetCallback<D>>) -> Self {
|
58
|
+
let inner = Arc::new(Provider::new(callback.clone()));
|
59
|
+
Self {
|
60
|
+
inner,
|
61
|
+
callback,
|
62
|
+
pending_requests: PendingCount::new(),
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
pub fn is_empty(&self) -> bool {
|
67
|
+
self.pending_requests.is_empty()
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
impl<D: Send + Sync + 'static> NetProvider<D> for ErrorHandlingProvider<D> {
|
72
|
+
fn fetch(&self, doc_id: usize, request: Request, handler: BoxedHandler<D>) {
|
73
|
+
self.pending_requests.increment();
|
74
|
+
|
75
|
+
let callback = self.callback.clone();
|
76
|
+
let request_url = request.url.to_string();
|
77
|
+
let pending_counter = self.pending_requests.clone();
|
78
|
+
|
79
|
+
self.inner.fetch_with_callback(request, Box::new(move |fetch_result| {
|
80
|
+
pending_counter.decrement();
|
81
|
+
|
82
|
+
match fetch_result {
|
83
|
+
Ok((_url, bytes)) => {
|
84
|
+
println!("Fetched {}", request_url);
|
85
|
+
handler.bytes(doc_id, bytes, callback);
|
86
|
+
}
|
87
|
+
Err(e) => {
|
88
|
+
let error_msg = Some(format!("Failed to fetch {}: {:?}", request_url, e));
|
89
|
+
callback.call(doc_id, Err(error_msg));
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}));
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
pub struct NetFetcher {
|
97
|
+
provider: Arc<ErrorHandlingProvider<Resource>>,
|
98
|
+
receiver: UnboundedReceiver<(usize, Result<Resource, Option<String>>)>,
|
99
|
+
}
|
100
|
+
|
101
|
+
impl NetFetcher {
|
102
|
+
pub fn new() -> Self {
|
103
|
+
let (receiver, callback) = ErrorHandlingCallback::new();
|
104
|
+
let callback = Arc::new(callback);
|
105
|
+
let provider = Arc::new(ErrorHandlingProvider::new(callback));
|
106
|
+
|
107
|
+
Self { provider, receiver }
|
108
|
+
}
|
109
|
+
|
110
|
+
pub fn get_provider(&self) -> Arc<dyn NetProvider<Resource>> {
|
111
|
+
Arc::clone(&self.provider) as Arc<dyn NetProvider<Resource>>
|
112
|
+
}
|
113
|
+
|
114
|
+
pub async fn fetch_resources(&mut self, document: &mut HtmlDocument) {
|
115
|
+
loop {
|
116
|
+
// Synchronous fetch before checking is_empty to avoid race condition
|
117
|
+
// where is_empty's reference counting thinks there is nothing to
|
118
|
+
// process. This happens when the fetch fails very early.
|
119
|
+
let res = match self.receiver.try_recv() {
|
120
|
+
Ok((_, res)) => res,
|
121
|
+
Err(_) => {
|
122
|
+
if self.provider.is_empty() {
|
123
|
+
break;
|
124
|
+
}
|
125
|
+
|
126
|
+
match self.receiver.recv().await {
|
127
|
+
Some((_, res)) => res,
|
128
|
+
None => break,
|
129
|
+
}
|
130
|
+
}
|
131
|
+
};
|
132
|
+
|
133
|
+
match res {
|
134
|
+
Ok(res) => document.as_mut().load_resource(res),
|
135
|
+
Err(_) => {}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
}
|
data/ext/himg/src/options.rs
CHANGED
@@ -1,11 +1,30 @@
|
|
1
1
|
use crate::image_size::ImageSize;
|
2
2
|
|
3
|
-
use blitz_traits::ColorScheme;
|
3
|
+
use blitz_traits::shell::ColorScheme;
|
4
4
|
|
5
|
-
#[derive(Clone
|
5
|
+
#[derive(Clone)]
|
6
6
|
pub struct Options {
|
7
7
|
pub image_size: ImageSize,
|
8
8
|
pub color_scheme: ColorScheme,
|
9
|
-
pub
|
9
|
+
pub disable_fetch: bool,
|
10
|
+
pub base_url: Option<String>,
|
10
11
|
pub truncate: bool,
|
12
|
+
pub verbose: bool,
|
13
|
+
}
|
14
|
+
|
15
|
+
impl Options {
|
16
|
+
pub fn default() -> Self {
|
17
|
+
Self {
|
18
|
+
image_size: ImageSize {
|
19
|
+
width: 720,
|
20
|
+
height: 405,
|
21
|
+
hidpi_scale: 1.0,
|
22
|
+
},
|
23
|
+
truncate: true,
|
24
|
+
verbose: false,
|
25
|
+
base_url: None,
|
26
|
+
disable_fetch: false,
|
27
|
+
color_scheme: ColorScheme::Light,
|
28
|
+
}
|
29
|
+
}
|
11
30
|
}
|
data/ext/himg/src/renderer.rs
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
use crate::html_to_image::html_to_image;
|
2
2
|
use crate::options::Options;
|
3
3
|
use crate::writer::write_png;
|
4
|
-
use crate::logger::{TimedLogger};
|
4
|
+
use crate::logger::{Logger, NullLogger, TimedLogger};
|
5
5
|
|
6
6
|
pub fn render_blocking(html: String, options: Options) -> Result<Vec<u8>, std::io::Error> {
|
7
7
|
let runtime = tokio::runtime::Runtime::new()?;
|
@@ -11,12 +11,14 @@ pub fn render_blocking(html: String, options: Options) -> Result<Vec<u8>, std::i
|
|
11
11
|
|
12
12
|
// render_to_bytes, render_to_string, render_to_file, render_to_io
|
13
13
|
pub async fn render(html: String, options: Options) -> Result<Vec<u8>, std::io::Error> {
|
14
|
-
let mut logger =
|
14
|
+
let mut logger: Box<dyn Logger> = if options.verbose {
|
15
|
+
Box::new(TimedLogger::init())
|
16
|
+
} else {
|
17
|
+
Box::new(NullLogger{})
|
18
|
+
};
|
15
19
|
|
16
20
|
// Render to Image
|
17
|
-
|
18
|
-
let base_url = None;
|
19
|
-
let render_output = html_to_image(&html, base_url, options, &mut logger).await;
|
21
|
+
let render_output = html_to_image(&html, options, &mut *logger).await;
|
20
22
|
|
21
23
|
// Determine output path, and open a file at that path.
|
22
24
|
let mut output_buffer: Vec<u8> = Vec::new();
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module Himg
|
4
|
+
class BaseUrl
|
5
|
+
def initialize(path_or_url)
|
6
|
+
path_or_url = path_or_url.to_s.strip
|
7
|
+
return if path_or_url&.empty?
|
8
|
+
|
9
|
+
@url = URI.parse(path_or_url)
|
10
|
+
@url.scheme = "file" unless @url.scheme
|
11
|
+
|
12
|
+
raise Himg::Error, "Invalid base_url #{path_or_url}" if @url.path.empty?
|
13
|
+
|
14
|
+
@url.path += "/" unless @url.path.end_with?("/")
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@url&.to_s
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/himg/cli.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "himg"
|
3
|
+
require "open-uri"
|
4
|
+
|
5
|
+
module Himg
|
6
|
+
class CLI < Thor
|
7
|
+
desc "screenshot SOURCE_HTML DESTINATION_PNG [OPTIONS]", "Render HTML to a png screenshot"
|
8
|
+
|
9
|
+
option :width, type: :numeric, desc: "Sets the width of the rendered content.", default: 720
|
10
|
+
option :height, type: :numeric, desc: "Sets the desired height of the rendered output.", default: 405
|
11
|
+
option :truncate, type: :boolean, desc: "Keeps the image height fixed instead of expanding to include the full page.", default: true
|
12
|
+
option :verbose, type: :boolean, desc: "Enables detailed logging for debugging and profiling.", default: false
|
13
|
+
option :disable_fetch, type: :boolean, desc: "Skip fetching file/http resources (stylesheets, images, fonts, etc)", default: false
|
14
|
+
option :http_headers, desc: "HTTP Headers to use when fetching remote resource"
|
15
|
+
option :base_url, desc: "Base URL used to resolve relative URLs"
|
16
|
+
|
17
|
+
long_desc <<-LONGDESC
|
18
|
+
`himg screenshot` takes a path to an HTML file and will render a png image with the output.
|
19
|
+
|
20
|
+
It takes a SOURCE, which can be a file path or a URL to fetch.
|
21
|
+
|
22
|
+
The DESTINATION_PNG must be a local file path.
|
23
|
+
|
24
|
+
CAVEATS: This uses a lightweight HTML parser instead of a full browser, so does not support all features.
|
25
|
+
Additionally it does not use a JavaScript engine, so will screenshot the page as-is and would not work for all webpages.
|
26
|
+
LONGDESC
|
27
|
+
def screenshot(url, destination)
|
28
|
+
Document.new(url, options).load do |content|
|
29
|
+
png = Himg.render(content, **options.transform_keys(&:to_sym))
|
30
|
+
|
31
|
+
File.open(destination, "wb") { |f| f.write(png) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def self.exit_on_failure?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
class Document
|
42
|
+
def initialize(source, options)
|
43
|
+
@source = source
|
44
|
+
@options = options
|
45
|
+
end
|
46
|
+
|
47
|
+
def http_url?
|
48
|
+
@source =~ %r{\Ahttps?\://}
|
49
|
+
end
|
50
|
+
|
51
|
+
def load(&block)
|
52
|
+
if http_url?
|
53
|
+
URI.send(:open, @source) do |input|
|
54
|
+
yield(input.binmode.read)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
File.open(@source) do |f|
|
58
|
+
yield(f.read)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -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,
|
33
|
-
|
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
data/lib/himg.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "himg/version"
|
4
|
+
require_relative "himg/base_url"
|
4
5
|
require "himg/railtie" if defined?(Rails::Railtie)
|
5
6
|
|
6
7
|
# Attempt to load a versioned extension based on the Ruby version.
|
@@ -16,9 +17,10 @@ end
|
|
16
17
|
#
|
17
18
|
# Converts HTML to an Image for a minimal subset of HTML and CSS
|
18
19
|
module Himg
|
20
|
+
RENDER_OPTIONS = %i[width height truncate verbose].freeze
|
19
21
|
class Error < StandardError; end
|
20
22
|
|
21
|
-
def self.render(html, width: 720, height: 405, truncate: true)
|
22
|
-
render_to_string(html, "width" => width, "height" => height, "truncate" => truncate)
|
23
|
+
def self.render(html, width: 720, height: 405, truncate: true, verbose: false, base_url: nil, disable_fetch: false)
|
24
|
+
render_to_string(html, "width" => width.to_i, "height" => height.to_i, "truncate" => truncate, "verbose" => verbose, "base_url" => BaseUrl.new(base_url).to_s, "disable_fetch" => disable_fetch)
|
23
25
|
end
|
24
26
|
end
|