svg2img 0.1.1 → 0.2.0

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.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -1
  3. data/ext/svg2img/src/lib.rs +65 -46
  4. data/lib/svg2img/version.rb +1 -1
  5. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.lock +1607 -0
  6. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.toml +7 -0
  7. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/ext/svg2img/Cargo.toml +17 -0
  8. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.lock +1607 -0
  9. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.toml +7 -0
  10. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/ext/svg2img/Cargo.toml +17 -0
  11. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.lock +1607 -0
  12. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.toml +7 -0
  13. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/ext/svg2img/Cargo.toml +17 -0
  14. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.lock +1607 -0
  15. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.toml +7 -0
  16. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/ext/svg2img/Cargo.toml +17 -0
  17. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.lock +1607 -0
  18. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.toml +7 -0
  19. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/ext/svg2img/Cargo.toml +17 -0
  20. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.lock +1607 -0
  21. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.toml +7 -0
  22. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/ext/svg2img/Cargo.toml +17 -0
  23. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.lock +1607 -0
  24. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/Cargo.toml +7 -0
  25. data/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/tmp/arm64-darwin23/stage/ext/svg2img/Cargo.toml +17 -0
  26. metadata +22 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d79ded4df5c7da3f90781d4a55545909261183b4ab6759baf2e20094ca48011
4
- data.tar.gz: d2bb132f2fa76a9f7e2754d37615afe67f111cb9e96b2534e298233620d21c05
3
+ metadata.gz: 3dfdedc801e9a2e74e277e283c199257d254a2f7f729594a3a89fad20709aef0
4
+ data.tar.gz: a8bdfeb03459e7a73b22d8c5c4900efb4513c344efecfe1cdf7df44c4938ba8f
5
5
  SHA512:
6
- metadata.gz: ee228f18a63c3b04969e8b3188b5b3a0c9b9a1a0c8cd3b89d0a8485e87ef27490aebe39cf9ef22944a8185a9540086569d0402b0b4da570a579289740dfbb706
7
- data.tar.gz: 06dc50fca47cce15698f4f1ab07b9d794986e3f141ba077586916a3e64c4afd7c84bd10655bb7af702da2f8f20dbb75619ed7d934bada4aa8dad35d7d138eb1e
6
+ metadata.gz: 90d0d0e131dc62c81a7354194b742a6a35e0ff4516250c2fb1cab86f4247143d03a79d4bdc56a1fc5c1ac45e5a2db0f6390f51e176528679c8d5a5176ee07581
7
+ data.tar.gz: '08f65ce21a539ec53a5aa675c98bb9bf8981d14d890259651af38cde50faa2739db1224137f28a32afcd97b3b2aa74da770acc0640fc0e8559c1ea13135311ca'
data/README.md CHANGED
@@ -55,7 +55,7 @@ png_path = Svg2Img.process_svg(circle_svg, output_format: :png)
55
55
 
56
56
  # Rails example
57
57
  data = Rails.cache.fetch([some, deps]) do
58
- png_path = Svg2Img.process_svg(circle_svg, output_format: :png)
58
+ png_path = Svg2Img.process_svg(circle_svg, output_format: :png, size: proc {|_svg_width, _svg_height| [256, 256]})
59
59
  png_data = File.binread(png_path)
60
60
  File.delete(png_path)
61
61
  png_data
@@ -63,6 +63,11 @@ end
63
63
  send_data(png_data, type: 'image/png', disposition: 'inline')
64
64
  ```
65
65
 
66
+ ### Options
67
+
68
+ - `output_format` - output format, one of `:png`, `:jpg`, `:webp`, `:gif`
69
+ - `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.
70
+
66
71
  ## Development
67
72
 
68
73
  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.
@@ -1,6 +1,6 @@
1
1
  use anyhow::Context;
2
- use image::{DynamicImage, GenericImageView, ImageBuffer};
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
- max_width: get_option(&options, "max_width")?,
32
- max_height: get_option(&options, "max_height")?,
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
- max_width: Option<u32>,
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 tree = resvg::usvg::Tree::from_data(bytes, &resvg::usvg::Options::default())
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
- const TARGET_SIZE: u32 = 512;
142
- let mut pixmap = resvg::tiny_skia::Pixmap::new(TARGET_SIZE, TARGET_SIZE)
143
- .context("Failed to create Pixmap for SVG rendering")?;
144
- let ratio = tree.size().width() / tree.size().height();
145
- let scaled_width = if ratio > 1.0 {
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
- TARGET_SIZE
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: scaled_width as f32 / tree.size().width(),
159
- sy: scaled_height as f32 / tree.size().height(),
160
- tx: (TARGET_SIZE - scaled_width) as f32 / 2.0,
161
- ty: (TARGET_SIZE - scaled_height) as f32 / 2.0,
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(&tree, transform, &mut pixmap.as_mut());
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
- rgba_to_image(pixmap.width(), pixmap.height(), pixmap.data())
200
+ pixmap_to_image(pixmap.width(), pixmap.height(), pixmap.data())
182
201
  }
183
202
 
184
- fn rgba_to_image(width: u32, height: u32, data: &[u8]) -> Result<DynamicImage, anyhow::Error> {
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Svg2Img
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end