svg2img 0.1.1 → 0.2.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 +4 -4
- data/README.md +13 -2
- data/ext/svg2img/src/lib.rs +65 -46
- data/lib/svg2img/version.rb +1 -1
- metadata +1 -4
- data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.lock +0 -1607
- data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.toml +0 -7
- data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/ext/svg2img/Cargo.toml +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18e78e2be604a584bdf8e325b2f007b46505e6d2d74cd584d55fa460aecc31d5
|
4
|
+
data.tar.gz: 6bacef7baabce46157cbc3d1af5dbefaae727e8726a1f35495c2d35f1d54cff1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c34f340977be8cdf8c4fd8e2a49e15a5409bdbcb770d555bc984d085ee1e5de95d16f37584d06ff49fb4c3b72b50d1c7ef23393e2178a9024b5821dc14a202f
|
7
|
+
data.tar.gz: 1126a561399fb919c61ea017d7cd1d910ab4422ab51c568bf9482e7c421b9d56e532658dc722b293264fc3b67cbf2701c1d7e0b605f1c2a0e3205915c340c4e8
|
data/README.md
CHANGED
@@ -40,6 +40,11 @@ bundle lock --add-platform arm64-darwin # Apple Silicon MacOS (i.e. M1)
|
|
40
40
|
|
41
41
|
## Usage
|
42
42
|
|
43
|
+
```ruby
|
44
|
+
require "svg2img"
|
45
|
+
Svg2Img.process_svg(svg_string, options)
|
46
|
+
```
|
47
|
+
|
43
48
|
Example usage:
|
44
49
|
|
45
50
|
```ruby
|
@@ -50,12 +55,12 @@ circle_svg = <<~SVG
|
|
50
55
|
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
|
51
56
|
</svg>
|
52
57
|
SVG
|
53
|
-
png_path = Svg2Img.process_svg(circle_svg,
|
58
|
+
png_path = Svg2Img.process_svg(circle_svg, format: :png)
|
54
59
|
# png_path is a path to the generated PNG file
|
55
60
|
|
56
61
|
# Rails example
|
57
62
|
data = Rails.cache.fetch([some, deps]) do
|
58
|
-
png_path = Svg2Img.process_svg(circle_svg,
|
63
|
+
png_path = Svg2Img.process_svg(circle_svg, format: :png, size: proc {|_svg_width, _svg_height| [256, 256]})
|
59
64
|
png_data = File.binread(png_path)
|
60
65
|
File.delete(png_path)
|
61
66
|
png_data
|
@@ -63,6 +68,12 @@ end
|
|
63
68
|
send_data(png_data, type: 'image/png', disposition: 'inline')
|
64
69
|
```
|
65
70
|
|
71
|
+
### Options
|
72
|
+
|
73
|
+
- `format` - output format, one of `:png`, `:jpg`, `:webp`, `:gif`
|
74
|
+
- `output_path` - path to the output image. If not provided, a temporary file will be created and the path to it will be returned.
|
75
|
+
- `size` - size of the output image as a proc that receives the width and height of the SVG and returns an array with the width and height of the output image. If the provides size has a different aspect ratio than the SVG, the image will be resized to fit in the center of the provided size. If not provided, the output image will have the same size as the SVG.
|
76
|
+
|
66
77
|
## Development
|
67
78
|
|
68
79
|
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/ext/svg2img/src/lib.rs
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
use anyhow::Context;
|
2
|
-
use image::{DynamicImage,
|
3
|
-
use magnus::{function, prelude::*, Error, Ruby};
|
2
|
+
use image::{DynamicImage, ImageBuffer};
|
3
|
+
use magnus::{block::Proc, function, prelude::*, Error, Ruby};
|
4
4
|
use std::panic::{self, AssertUnwindSafe};
|
5
5
|
|
6
6
|
#[magnus::init]
|
@@ -28,8 +28,9 @@ fn process_svg_rb(svg: String, options: magnus::RHash) -> Result<String, magnus:
|
|
28
28
|
};
|
29
29
|
}
|
30
30
|
let options = Options {
|
31
|
-
|
32
|
-
|
31
|
+
size: get_option::<Proc>(&options, "size")?
|
32
|
+
.map(convert_size_proc)
|
33
|
+
.unwrap_or_else(default_size),
|
33
34
|
format,
|
34
35
|
output_path: get_option(&options, "output_path")?,
|
35
36
|
};
|
@@ -76,32 +77,30 @@ fn get_string_option(options: &magnus::RHash, key: &str) -> Result<Option<String
|
|
76
77
|
Ok(None)
|
77
78
|
}
|
78
79
|
|
80
|
+
type ProcessSize = Box<dyn FnOnce(u32, u32) -> Result<(u32, u32), anyhow::Error>>;
|
81
|
+
fn default_size() -> ProcessSize {
|
82
|
+
Box::new(|width, height| Ok((width, height)))
|
83
|
+
}
|
84
|
+
fn convert_size_proc(proc: Proc) -> ProcessSize {
|
85
|
+
Box::new(move |width, height| {
|
86
|
+
let result: Result<(i64, i64), magnus::Error> = proc.call((width as i64, height as i64));
|
87
|
+
match result {
|
88
|
+
Ok((width, height)) => Ok((width as u32, height as u32)),
|
89
|
+
Err(err) => Err(anyhow::anyhow!(
|
90
|
+
"svg2img: Failed to call size proc: {err:?}"
|
91
|
+
)),
|
92
|
+
}
|
93
|
+
})
|
94
|
+
}
|
95
|
+
|
79
96
|
struct Options {
|
80
|
-
|
81
|
-
max_height: Option<u32>,
|
97
|
+
size: ProcessSize,
|
82
98
|
format: image::ImageFormat,
|
83
99
|
output_path: Option<String>,
|
84
100
|
}
|
85
101
|
|
86
102
|
fn process_svg(svg: String, options: Options) -> Result<String, anyhow::Error> {
|
87
|
-
let image = image_from_svg(svg.as_bytes())?;
|
88
|
-
let (old_width, old_height) = image.dimensions();
|
89
|
-
|
90
|
-
let (mut width, mut height) = (old_width, old_height);
|
91
|
-
if let Some(max_width) = options.max_width {
|
92
|
-
if width > max_width {
|
93
|
-
height = height * max_width / width;
|
94
|
-
width = max_width;
|
95
|
-
}
|
96
|
-
}
|
97
|
-
if let Some(max_height) = options.max_height {
|
98
|
-
if height > max_height {
|
99
|
-
width = width * max_height / height;
|
100
|
-
height = max_height;
|
101
|
-
}
|
102
|
-
}
|
103
|
-
|
104
|
-
let mut image = image.resize_exact(width, height, image::imageops::FilterType::Lanczos3);
|
103
|
+
let mut image = image_from_svg(svg.as_bytes(), options.size)?;
|
105
104
|
|
106
105
|
if options.format == image::ImageFormat::Jpeg {
|
107
106
|
// Convert from rgba8 to rgb8
|
@@ -134,36 +133,56 @@ fn process_svg(svg: String, options: Options) -> Result<String, anyhow::Error> {
|
|
134
133
|
Ok(output_path)
|
135
134
|
}
|
136
135
|
|
137
|
-
fn image_from_svg(bytes: &[u8]) -> Result<DynamicImage, anyhow::Error> {
|
138
|
-
let
|
136
|
+
fn image_from_svg(bytes: &[u8], size: ProcessSize) -> Result<DynamicImage, anyhow::Error> {
|
137
|
+
let svg = resvg::usvg::Tree::from_data(bytes, &resvg::usvg::Options::default())
|
139
138
|
.context("Failed to parse SVG")?;
|
139
|
+
let svg_width = svg.size().width();
|
140
|
+
let svg_height = svg.size().height();
|
141
|
+
let svg_ratio = svg_width / svg_height;
|
140
142
|
|
141
|
-
|
142
|
-
let
|
143
|
-
|
144
|
-
let
|
145
|
-
|
146
|
-
TARGET_SIZE
|
147
|
-
} else {
|
148
|
-
(TARGET_SIZE as f32 * ratio).round() as u32
|
149
|
-
};
|
150
|
-
let scaled_height = if ratio > 1.0 {
|
151
|
-
(TARGET_SIZE as f32 / ratio).round() as u32
|
143
|
+
let (image_width, image_height) = size(svg_width as u32, svg_height as u32)?;
|
144
|
+
let image_ratio = image_width as f32 / image_height as f32;
|
145
|
+
|
146
|
+
let scale = if svg_ratio > image_ratio {
|
147
|
+
image_width as f32 / svg_width
|
152
148
|
} else {
|
153
|
-
|
149
|
+
image_height as f32 / svg_height
|
154
150
|
};
|
151
|
+
let rendered_width = svg_width * scale;
|
152
|
+
let rendered_height = svg_height * scale;
|
153
|
+
let tx = (image_width as f32 - rendered_width) / 2.0;
|
154
|
+
let ty = (image_height as f32 - rendered_height) / 2.0;
|
155
|
+
|
156
|
+
// panic!(
|
157
|
+
// r#"
|
158
|
+
// svg_width: {svg_width}
|
159
|
+
// svg_height: {svg_height}
|
160
|
+
// svg_ratio: {svg_ratio}
|
161
|
+
// image_width: {image_width}
|
162
|
+
// image_height: {image_height}
|
163
|
+
// image_ratio: {image_ratio}
|
164
|
+
// rendered_width: {rendered_width}
|
165
|
+
// rendered_height: {rendered_height}
|
166
|
+
// scale: {scale}
|
167
|
+
// tx: {tx}
|
168
|
+
// ty: {ty}
|
169
|
+
// "#
|
170
|
+
// );
|
155
171
|
|
156
172
|
// Scale svg and place it centered
|
157
|
-
let transform = resvg::tiny_skia::Transform {
|
158
|
-
sx:
|
159
|
-
sy:
|
160
|
-
tx
|
161
|
-
ty
|
173
|
+
let transform: resvg::usvg::Transform = resvg::tiny_skia::Transform {
|
174
|
+
sx: scale,
|
175
|
+
sy: scale,
|
176
|
+
tx,
|
177
|
+
ty,
|
162
178
|
..Default::default()
|
163
179
|
};
|
164
180
|
|
181
|
+
let mut pixmap = resvg::tiny_skia::Pixmap::new(image_width, image_height)
|
182
|
+
.context("Failed to create Pixmap for SVG rendering")?;
|
183
|
+
|
165
184
|
let result = panic::catch_unwind(AssertUnwindSafe(|| {
|
166
|
-
resvg::render(&
|
185
|
+
resvg::render(&svg, transform, &mut pixmap.as_mut());
|
167
186
|
}));
|
168
187
|
if let Err(panic) = result {
|
169
188
|
let panic_message = panic
|
@@ -178,10 +197,10 @@ fn image_from_svg(bytes: &[u8]) -> Result<DynamicImage, anyhow::Error> {
|
|
178
197
|
return Err(anyhow::anyhow!("SVG rendering panicked: {}", panic_message));
|
179
198
|
}
|
180
199
|
|
181
|
-
|
200
|
+
pixmap_to_image(pixmap.width(), pixmap.height(), pixmap.data())
|
182
201
|
}
|
183
202
|
|
184
|
-
fn
|
203
|
+
fn pixmap_to_image(width: u32, height: u32, data: &[u8]) -> Result<DynamicImage, anyhow::Error> {
|
185
204
|
let mut image_data = Vec::with_capacity((width * height * 4) as usize);
|
186
205
|
for pixel in data.chunks(4) {
|
187
206
|
image_data.push(pixel[0]); // R
|
data/lib/svg2img/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: svg2img
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Orvar Segerström
|
@@ -44,9 +44,6 @@ files:
|
|
44
44
|
- tmp/arm64-darwin23/stage/Cargo.lock
|
45
45
|
- tmp/arm64-darwin23/stage/Cargo.toml
|
46
46
|
- tmp/arm64-darwin23/stage/ext/svg2img/Cargo.toml
|
47
|
-
- tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.lock
|
48
|
-
- tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.toml
|
49
|
-
- tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/ext/svg2img/Cargo.toml
|
50
47
|
homepage: https://github.com/0rvar/svg2img-rb
|
51
48
|
licenses:
|
52
49
|
- MIT
|