rucaptcha 3.2.4-arm64-darwin → 3.3.0-arm64-darwin

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: e4e44cb2afef98bea4b50768ef15d2f1cef1a6a816e38c33d56097d3a2f29926
4
- data.tar.gz: 8574fba93095adabd1a01b304e3fa27bb40ab6759034d1656bb5ab02cfee7e82
3
+ metadata.gz: f734a82bb195ff63cfa6c36bd1e0c6d0472ce2257d28e9e62939644a9aa0a634
4
+ data.tar.gz: 6d6a53ca5a00667ec3585d4811bd621a745842f2a1921af0d76720abfab485a5
5
5
  SHA512:
6
- metadata.gz: 253a154c601a5f627c2ff8834bb1f751fa8bfaf01249601076cdcc39bc90259d92bfddb762a4789385d41701586f394b8044c20bee212b6de71cb8944c366e7f
7
- data.tar.gz: de344b77360f0497aa351766cf3984f12fe0897fb1e2eef335e1165d54e33783aaea03afdb8afe66fa3b959862ac4691c86192d747d1ce43f613a32331f3b78b
6
+ metadata.gz: 9d4116b546a0defeff73685cbc8fc1004cb79565aaae8ffe9b4bb7f6df6d9a81f31667d8ba0c2d970f1f3a17867382cac73b960703b2c144c22d68c15ef23a85
7
+ data.tar.gz: 91a4ca9deb27121f001983516e0bc4544cb5add041d805bea8159cceac736ae7d298ee14dbbd49325b9a03d1aa3d3c8b5c7cb5aee7205d33fd563c5b8413ee3f
data/README.md CHANGED
@@ -59,6 +59,16 @@ RuCaptcha.configure do
59
59
  # Enable or disable noise, default: false
60
60
  # self.noise = false
61
61
 
62
+ # Enable or disable circle background, default: true
63
+ # self.circle = true
64
+
65
+ # Set the difficulty level, default: 5, allows: [1..10].
66
+ # Only valid when noise is enabled
67
+ # self.difficulty = 5
68
+
69
+ # Set the case sensitive, default: false
70
+ # self.case_sensitive = false
71
+
62
72
  # Set the image format, default: png, allows: [jpeg, png, webp]
63
73
  # self.format = 'png'
64
74
 
@@ -181,11 +191,13 @@ end
181
191
 
182
192
  ## Performance
183
193
 
194
+ > Based on MacBook Pro (Apple M3)
195
+
184
196
  `rake benchmark` to run benchmark test.
185
197
 
186
198
  ```
187
199
  Warming up --------------------------------------
188
- Generate image 51.000 i/100ms
200
+ Generate image 84.000 i/100ms
189
201
  Calculating -------------------------------------
190
- Generate image 526.350 2.5%) i/s - 2.652k in 5.041681s
202
+ Generate image 859.075 1.5%) i/s (1.16 ms/i) - 4.368k in 5.085775s
191
203
  ```
data/Rakefile CHANGED
@@ -30,7 +30,7 @@ task default: :spec
30
30
 
31
31
  def create_captcha(length = 5, difficulty = 5)
32
32
  require "rucaptcha"
33
- RuCaptchaCore.create(length, difficulty, false, false, "png")
33
+ RuCaptchaCore.create(length, difficulty, false, false, false, "png")
34
34
  end
35
35
 
36
36
  task :preview do
@@ -63,10 +63,10 @@ task :benchmark do
63
63
  require "rucaptcha"
64
64
  require "benchmark/ips"
65
65
 
66
- RuCaptchaCore.create(5, 5, true, true, "png")
66
+ RuCaptchaCore.create(5, 5, true, true, true, "png")
67
67
 
68
68
  Benchmark.ips do |x|
69
- x.report("Generate image") { RuCaptchaCore.create(5, 5, true, true, "png") }
69
+ x.report("Generate image") { RuCaptchaCore.create(5, 5, true, true, true, "png") }
70
70
  x.compare!
71
71
  end
72
72
  end
@@ -9,6 +9,6 @@ crate-type = ["cdylib"]
9
9
  [dependencies]
10
10
  image = "0.24"
11
11
  imageproc = "0.23"
12
- magnus = "0.6"
12
+ magnus = "0.7"
13
13
  rand = "0.8.5"
14
14
  rusttype = "0.9.2"
@@ -1,7 +1,11 @@
1
1
  use image::{ImageBuffer, Rgba};
2
+ use imageproc::{
3
+ drawing::{draw_cubic_bezier_curve_mut, draw_filled_ellipse_mut},
4
+ noise::gaussian_noise_mut,
5
+ };
2
6
  use rand::{thread_rng, Rng};
3
7
  use rusttype::{Font, Scale};
4
- use std::io::Cursor;
8
+ use std::{io::Cursor, sync::LazyLock};
5
9
 
6
10
  static BASIC_CHAR: [char; 54] = [
7
11
  '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M',
@@ -9,9 +13,6 @@ static BASIC_CHAR: [char; 54] = [
9
13
  'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
10
14
  ];
11
15
 
12
- static FONT_BYTES1: &[u8; 145008] = include_bytes!("../fonts/FuzzyBubbles-Regular.ttf");
13
- static FONT_BYTES2: &[u8; 37792] = include_bytes!("../fonts/Handlee-Regular.ttf");
14
-
15
16
  // https://coolors.co/cc0b8f-7c0abe-5700c8-3c2ea4-3d56a8-3fa67e-45bb30-69d003-a0d003-d8db02
16
17
  static COLORS: [(u8, u8, u8, u8); 14] = [
17
18
  (197, 166, 3, 255),
@@ -33,32 +34,34 @@ static COLORS: [(u8, u8, u8, u8); 14] = [
33
34
  static SCALE_SM: u32 = 32;
34
35
  static SCALE_MD: u32 = 45;
35
36
  static SCALE_LG: u32 = 55;
37
+ static FONT_0: LazyLock<Font> = LazyLock::new(|| {
38
+ Font::try_from_bytes(include_bytes!("../fonts/FuzzyBubbles-Regular.ttf")).unwrap()
39
+ });
40
+ static FONT_1: LazyLock<Font> =
41
+ LazyLock::new(|| Font::try_from_bytes(include_bytes!("../fonts/Handlee-Regular.ttf")).unwrap());
36
42
 
43
+ #[inline(always)]
37
44
  fn rand_num(len: usize) -> usize {
38
45
  let mut rng = thread_rng();
39
46
  rng.gen_range(0..=len)
40
47
  }
41
48
 
42
- fn get_captcha(len: usize) -> Vec<String> {
43
- let mut res = vec![];
49
+ /// Generate a random captcha string with a given length
50
+ #[inline]
51
+ fn rand_captcha(len: usize) -> String {
52
+ let mut result = String::with_capacity(len);
53
+ let seed = BASIC_CHAR.len() - 1;
44
54
  for _ in 0..len {
45
- let rnd = rand_num(53);
46
- res.push(BASIC_CHAR[rnd].to_string())
55
+ let rnd = rand_num(seed);
56
+ result.push(BASIC_CHAR[rnd])
47
57
  }
48
- res
49
- }
50
-
51
- #[allow(unused)]
52
- fn get_color() -> Rgba<u8> {
53
- let rnd = rand_num(COLORS.len() - 1);
54
- let c = COLORS[rnd];
55
- Rgba([c.0, c.1, c.2, c.3])
58
+ result
56
59
  }
57
60
 
58
- fn get_colors(num: usize) -> Vec<Rgba<u8>> {
61
+ fn get_colors(len: usize) -> Vec<Rgba<u8>> {
59
62
  let rnd = rand_num(COLORS.len());
60
- let mut out = vec![];
61
- for i in 0..num {
63
+ let mut out = Vec::with_capacity(len);
64
+ for i in 0..len {
62
65
  let c = COLORS[(rnd + i) % COLORS.len()];
63
66
  out.push(Rgba([c.0, c.1, c.2, c.3]))
64
67
  }
@@ -66,78 +69,11 @@ fn get_colors(num: usize) -> Vec<Rgba<u8>> {
66
69
  out
67
70
  }
68
71
 
72
+ #[inline(always)]
69
73
  fn get_next(min: f32, max: u32) -> f32 {
70
74
  min + rand_num(max as usize - min as usize) as f32
71
75
  }
72
76
 
73
- fn get_font() -> Font<'static> {
74
- match rand_num(2) {
75
- 0 => Font::try_from_bytes(FONT_BYTES1).unwrap(),
76
- 1 => Font::try_from_bytes(FONT_BYTES2).unwrap(),
77
- _ => Font::try_from_bytes(FONT_BYTES1).unwrap(),
78
- }
79
- }
80
-
81
- fn get_image(width: usize, height: usize) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
82
- ImageBuffer::from_fn(width as u32, height as u32, |_, _| {
83
- image::Rgba([255, 255, 255, 255])
84
- })
85
- }
86
-
87
- fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, lines: bool) {
88
- let c = (image.width() - 20) / res.len() as u32;
89
- let y = image.height() / 3 - 15;
90
-
91
- let h = image.height() as f32;
92
-
93
- let scale = match res.len() {
94
- 1..=3 => SCALE_LG,
95
- 4..=5 => SCALE_MD,
96
- _ => SCALE_SM,
97
- } as f32;
98
-
99
- let colors = get_colors(res.len());
100
- let line_colors = get_colors(res.len());
101
-
102
- let xscale = scale - rand_num((scale * 0.2) as usize) as f32;
103
- let yscale = h as f32 - rand_num((h * 0.2) as usize) as f32;
104
-
105
- // Draw line, ellipse first as background
106
- for (i, _) in res.iter().enumerate() {
107
- let line_color = line_colors[i];
108
-
109
- if lines {
110
- draw_interference_line(1, image, line_color);
111
- }
112
- draw_interference_ellipse(1, image, line_color);
113
- }
114
-
115
- // Draw text
116
- for (i, _) in res.iter().enumerate() {
117
- let text = &res[i];
118
-
119
- let color = colors[i];
120
- let font = get_font();
121
-
122
- for j in 0..(rand_num(3) + 1) as i32 {
123
- // Draw text again with offset
124
- let offset = j * (rand_num(2) as i32);
125
- imageproc::drawing::draw_text_mut(
126
- image,
127
- color,
128
- 10 + offset + (i as u32 * c) as i32,
129
- y as i32 as i32,
130
- Scale {
131
- x: xscale + offset as f32,
132
- y: yscale as f32,
133
- },
134
- &font,
135
- text,
136
- );
137
- }
138
- }
139
- }
140
-
141
77
  fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, color: Rgba<u8>) {
142
78
  for _ in 0..num {
143
79
  let width = image.width();
@@ -154,7 +90,7 @@ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>
154
90
  let ctrl_x2 = get_next((width / 12) as f32, width / 12 * 3);
155
91
  let ctrl_y2 = get_next(x1, height - 5);
156
92
  // Randomly draw bezier curves
157
- imageproc::drawing::draw_cubic_bezier_curve_mut(
93
+ draw_cubic_bezier_curve_mut(
158
94
  image,
159
95
  (x1, y1),
160
96
  (x2, y2),
@@ -176,7 +112,7 @@ fn draw_interference_ellipse(
176
112
  let x = rand_num((image.width() - 25) as usize) as i32;
177
113
  let y = rand_num((image.height() - 15) as usize) as i32;
178
114
 
179
- imageproc::drawing::draw_filled_ellipse_mut(image, (x, y), w, w, color);
115
+ draw_filled_ellipse_mut(image, (x, y), w, w, color);
180
116
  }
181
117
  }
182
118
 
@@ -187,16 +123,17 @@ pub struct Captcha {
187
123
 
188
124
  pub struct CaptchaBuilder {
189
125
  length: usize,
190
- width: usize,
191
- height: usize,
126
+ width: u32,
127
+ height: u32,
192
128
  complexity: usize,
193
129
  line: bool,
194
130
  noise: bool,
131
+ circle: bool,
195
132
  format: image::ImageFormat,
196
133
  }
197
134
 
198
- impl CaptchaBuilder {
199
- pub fn new() -> Self {
135
+ impl Default for CaptchaBuilder {
136
+ fn default() -> Self {
200
137
  CaptchaBuilder {
201
138
  length: 4,
202
139
  width: 220,
@@ -204,9 +141,16 @@ impl CaptchaBuilder {
204
141
  complexity: 5,
205
142
  line: true,
206
143
  noise: false,
144
+ circle: true,
207
145
  format: image::ImageFormat::Png,
208
146
  }
209
147
  }
148
+ }
149
+
150
+ impl CaptchaBuilder {
151
+ pub fn new() -> Self {
152
+ Self::default()
153
+ }
210
154
 
211
155
  pub fn length(mut self, length: usize) -> Self {
212
156
  self.length = length;
@@ -223,6 +167,11 @@ impl CaptchaBuilder {
223
167
  self
224
168
  }
225
169
 
170
+ pub fn circle(mut self, circle: bool) -> Self {
171
+ self.circle = circle;
172
+ self
173
+ }
174
+
226
175
  pub fn format(mut self, format: &str) -> Self {
227
176
  self.format = match format {
228
177
  "png" => image::ImageFormat::Png,
@@ -235,32 +184,89 @@ impl CaptchaBuilder {
235
184
  }
236
185
 
237
186
  pub fn complexity(mut self, complexity: usize) -> Self {
238
- let mut complexity = complexity;
239
- if complexity > 10 {
240
- complexity = 10;
187
+ self.complexity = complexity.clamp(1, 10);
188
+ self
189
+ }
190
+
191
+ fn cyclic_write_character(
192
+ &self,
193
+ captcha: &str,
194
+ image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
195
+ lines: bool,
196
+ ) {
197
+ let c = (image.width() - 20) / captcha.len() as u32;
198
+ let y = image.height() / 3 - 15;
199
+
200
+ let h = image.height() as f32;
201
+
202
+ let scale = match captcha.len() {
203
+ 1..=3 => SCALE_LG,
204
+ 4..=5 => SCALE_MD,
205
+ _ => SCALE_SM,
206
+ } as f32;
207
+
208
+ let colors = get_colors(captcha.len());
209
+ let line_colors = get_colors(captcha.len());
210
+
211
+ let xscale = scale - rand_num((scale * 0.2) as usize) as f32;
212
+ let yscale = h - rand_num((h * 0.2) as usize) as f32;
213
+
214
+ // Draw line, ellipse first as background
215
+ if self.circle {
216
+ (0..captcha.len()).for_each(|i| {
217
+ let line_color = line_colors[i];
218
+
219
+ if lines {
220
+ draw_interference_line(1, image, line_color);
221
+ }
222
+ draw_interference_ellipse(1, image, line_color);
223
+ });
241
224
  }
242
- if complexity < 1 {
243
- complexity = 1;
225
+
226
+ let font = match rand_num(2) {
227
+ 0 => &FONT_0,
228
+ 1 => &FONT_1,
229
+ _ => &FONT_1,
230
+ };
231
+
232
+ // Draw text
233
+ for (i, ch) in captcha.chars().enumerate() {
234
+ let color = colors[i];
235
+
236
+ for j in 0..(rand_num(3) + 1) as i32 {
237
+ // Draw text again with offset
238
+ let offset = j * (rand_num(2) as i32);
239
+ imageproc::drawing::draw_text_mut(
240
+ image,
241
+ color,
242
+ 10 + offset + (i as u32 * c) as i32,
243
+ y as i32,
244
+ Scale {
245
+ x: xscale + offset as f32,
246
+ y: yscale as f32,
247
+ },
248
+ font,
249
+ &ch.to_string(),
250
+ );
251
+ }
244
252
  }
245
- self.complexity = complexity;
246
- self
247
253
  }
248
254
 
249
255
  pub fn build(self) -> Captcha {
250
256
  // Generate an array of captcha characters
251
- let res = get_captcha(self.length);
252
-
253
- let text = res.join("");
257
+ let text = rand_captcha(self.length);
254
258
 
255
259
  // Create a white background image
256
- let mut image = get_image(self.width, self.height);
260
+ let mut buf = ImageBuffer::from_fn(self.width, self.height, |_, _| {
261
+ image::Rgba([255, 255, 255, 255])
262
+ });
257
263
 
258
264
  // Loop to write the verification code string into the background image
259
- cyclic_write_character(&res, &mut image, self.line);
265
+ self.cyclic_write_character(&text, &mut buf, self.line);
260
266
 
261
267
  if self.noise {
262
- imageproc::noise::gaussian_noise_mut(
263
- &mut image,
268
+ gaussian_noise_mut(
269
+ &mut buf,
264
270
  (self.complexity - 1) as f64,
265
271
  ((10 * self.complexity) - 10) as f64,
266
272
  ((5 * self.complexity) - 5) as u64,
@@ -268,9 +274,8 @@ impl CaptchaBuilder {
268
274
  }
269
275
 
270
276
  let mut bytes: Vec<u8> = Vec::new();
271
- image
272
- .write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)
273
- .unwrap();
277
+ buf.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)
278
+ .expect("failed to write rucaptcha image into png");
274
279
 
275
280
  Captcha { text, image: bytes }
276
281
  }
@@ -7,6 +7,7 @@ pub fn create(
7
7
  difficulty: usize,
8
8
  line: bool,
9
9
  noise: bool,
10
+ circle: bool,
10
11
  format: String,
11
12
  ) -> (String, Vec<u8>) {
12
13
  let c = captcha::CaptchaBuilder::new();
@@ -15,6 +16,7 @@ pub fn create(
15
16
  .length(len)
16
17
  .line(line)
17
18
  .noise(noise)
19
+ .circle(circle)
18
20
  .format(&format)
19
21
  .build();
20
22
 
@@ -24,7 +26,7 @@ pub fn create(
24
26
  #[magnus::init]
25
27
  fn init() -> Result<(), Error> {
26
28
  let class = define_class("RuCaptchaCore", magnus::class::object())?;
27
- class.define_singleton_method("create", function!(create, 5))?;
29
+ class.define_singleton_method("create", function!(create, 6))?;
28
30
 
29
31
  Ok(())
30
32
  }
Binary file
Binary file
Binary file
@@ -13,11 +13,15 @@ module RuCaptcha
13
13
  attr_accessor :line
14
14
  # Enable or disable noise on captcha image, default: false
15
15
  attr_accessor :noise
16
+ # Enable or disable circle background on captcha image, default: true
17
+ attr_accessor :circle
16
18
  # Image format allow: ['jpeg', 'png', 'webp'], default: 'png'
17
19
  attr_accessor :format
18
20
  # skip_cache_store_check, default: false
19
21
  attr_accessor :skip_cache_store_check
20
22
  # custom rucaptcha mount path, default: '/rucaptcha'
21
23
  attr_accessor :mount_path
24
+ # Enable or disable case sensitive, default: false
25
+ attr_accessor :case_sensitive
22
26
  end
23
27
  end
@@ -62,10 +62,17 @@ module RuCaptcha
62
62
  return add_rucaptcha_validation_error if (Time.now.to_i - store_info[:time]) > RuCaptcha.config.expires_in
63
63
 
64
64
  # Make sure parama have captcha
65
- captcha = (opts[:captcha] || params[:_rucaptcha] || "").downcase.strip
65
+ captcha = (opts[:captcha] || params[:_rucaptcha] || "").strip
66
+ saved_code = store_info[:code]
67
+
66
68
  return add_rucaptcha_validation_error if captcha.blank?
67
69
 
68
- return add_rucaptcha_validation_error if captcha != store_info[:code]
70
+ unless RuCaptcha.config.case_sensitive
71
+ captcha.downcase!
72
+ saved_code.downcase!
73
+ end
74
+
75
+ return add_rucaptcha_validation_error if captcha != saved_code
69
76
 
70
77
  true
71
78
  end
@@ -1,3 +1,3 @@
1
1
  module RuCaptcha
2
- VERSION = "3.2.4"
2
+ VERSION = "3.3.0"
3
3
  end
data/lib/rucaptcha.rb CHANGED
@@ -29,9 +29,11 @@ module RuCaptcha
29
29
  @config.expires_in = 2.minutes
30
30
  @config.skip_cache_store_check = false
31
31
  @config.line = true
32
- @config.noise = true
32
+ @config.noise = false
33
+ @config.circle = true
33
34
  @config.format = "png"
34
35
  @config.mount_path = "/rucaptcha"
36
+ @config.case_sensitive = false
35
37
 
36
38
  @config.cache_store = if Rails.application
37
39
  Rails.application.config.cache_store
@@ -51,8 +53,12 @@ module RuCaptcha
51
53
 
52
54
  raise RuCaptcha::Errors::Configuration, "length config error, value must in 3..7" unless length.in?(3..7)
53
55
 
54
- result = RuCaptchaCore.create(length, config.difficulty || 5, config.line, config.noise, config.format)
55
- [result[0].downcase, result[1].pack("c*")]
56
+ result = RuCaptchaCore.create(length, config.difficulty || 5, config.line, config.noise, config.circle, config.format)
57
+ unless config.case_sensitive
58
+ result[0].downcase!
59
+ end
60
+
61
+ [result[0], result[1].pack("c*")]
56
62
  end
57
63
 
58
64
  def check_cache_store!
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rucaptcha
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.4
4
+ version: 3.3.0
5
5
  platform: arm64-darwin
6
6
  authors:
7
7
  - Jason Lee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-04 00:00:00.000000000 Z
11
+ date: 2025-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -24,21 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.2'
27
- force_ruby_platform: false
28
27
  - !ruby/object:Gem::Dependency
29
28
  name: rb_sys
30
29
  requirement: !ruby/object:Gem::Requirement
31
30
  requirements:
32
31
  - - ">="
33
32
  - !ruby/object:Gem::Version
34
- version: 0.9.86
33
+ version: 0.9.105
35
34
  type: :runtime
36
35
  prerelease: false
37
36
  version_requirements: !ruby/object:Gem::Requirement
38
37
  requirements:
39
38
  - - ">="
40
39
  - !ruby/object:Gem::Version
41
- version: 0.9.86
40
+ version: 0.9.105
42
41
  description:
43
42
  email: huacnlee@gmail.com
44
43
  executables: []
@@ -62,9 +61,9 @@ files:
62
61
  - ext/rucaptcha/src/captcha.rs
63
62
  - ext/rucaptcha/src/lib.rs
64
63
  - lib/rucaptcha.rb
65
- - lib/rucaptcha/3.1/rucaptcha.bundle
66
64
  - lib/rucaptcha/3.2/rucaptcha.bundle
67
65
  - lib/rucaptcha/3.3/rucaptcha.bundle
66
+ - lib/rucaptcha/3.4/rucaptcha.bundle
68
67
  - lib/rucaptcha/cache.rb
69
68
  - lib/rucaptcha/configuration.rb
70
69
  - lib/rucaptcha/controller_helpers.rb
@@ -84,17 +83,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
84
83
  requirements:
85
84
  - - ">="
86
85
  - !ruby/object:Gem::Version
87
- version: '3.1'
86
+ version: '3.2'
88
87
  - - "<"
89
88
  - !ruby/object:Gem::Version
90
- version: 3.4.dev
89
+ version: 3.5.dev
91
90
  required_rubygems_version: !ruby/object:Gem::Requirement
92
91
  requirements:
93
92
  - - ">="
94
93
  - !ruby/object:Gem::Version
95
94
  version: '0'
96
95
  requirements: []
97
- rubygems_version: 3.4.4
96
+ rubygems_version: 3.5.23
98
97
  signing_key:
99
98
  specification_version: 4
100
99
  summary: Captcha Gem for Rails, which generates captcha image by Rust.
Binary file