diff --git a/lib/rucaptcha/captcha.rb b/lib/rucaptcha/captcha.rb index 1687f8b..51bf2da 100644 --- a/lib/rucaptcha/captcha.rb +++ b/lib/rucaptcha/captcha.rb @@ -3,6 +3,7 @@ require 'open3' module RuCaptcha class Captcha class << self + # Genrate ranom RGB color def random_color if RuCaptcha.config.style == :colorful color1 = rand(56) + 15 @@ -16,32 +17,84 @@ module RuCaptcha end end + # Genrate random Captcha code def random_chars chars = SecureRandom.hex(RuCaptcha.config.len / 2).downcase chars.gsub!(/[0ol1]/i, (rand(8) + 2).to_s) chars end - def rand_line_top(text_top, font_size) - text_top + rand(font_size * 0.7).to_i - end - # Create Captcha image by code def create(code) chars = code.split('') - all_left = 20 - font_size = RuCaptcha.config.font_size - full_height = font_size - full_width = font_size * chars.size + full_width = RuCaptcha.config.font_size * chars.size + full_height = RuCaptcha.config.font_size size = "#{full_width}x#{full_height}" + + return convert_for_windows(size, code) if Gem.win_platform? + + opts = command_line_opts(chars, full_width) + convert(size, opts) + end + + private + + def command_line_opts(chars, full_width) + font_size = RuCaptcha.config.font_size + all_left = 20 half_width = full_width / 2 text_top = 0 text_left = 0 - (font_size * 0.28).to_i - stroke_width = (font_size * 0.05).to_i + 1 text_width = font_size + text_left - text_opts = [] - line_opts = [] + opts = { text: [], line: [] } + rgbs = uniq_rgbs_for_each_chars(chars) + + chars.each_with_index do |char, i| + rgb = RuCaptcha.config.style == :colorful ? rgbs[i] : rgbs[0] + text_color = "rgba(#{rgb.join(',')}, 1)" + line_color = "rgba(#{rgb.join(',')}, 0.6)" + opts[:text] << %(-fill '#{text_color}' -draw 'text #{(text_left + text_width) * i + all_left},#{text_top} "#{char}"') + + left_y = rand_line_top(text_top, font_size) + right_x = half_width + (half_width * 0.3).to_i + right_y = rand_line_top(text_top, font_size) + opts[:line] << %(-draw 'stroke #{line_color} line #{rand(10)},#{left_y} #{right_x},#{right_y}') + end + opts + end + + def convert(size, opts) + stroke_width = (RuCaptcha.config.font_size * 0.05).to_i + 1 + command = <<-CODE + convert -size #{size} \ + -strokewidth #{stroke_width} \ + #{opts[:line].join(' ')} \ + -pointsize #{RuCaptcha.config.font_size} -weight 500 \ + #{opts[:text].join(' ')} \ + -wave #{rand(2) + 3}x#{rand(2) + 1} \ + -rotate #{rand(10) - 5} \ + -gravity NorthWest -sketch 1x10+#{rand(2)} \ + -fill none \ + -implode #{RuCaptcha.config.implode} -trim label:- png:- + CODE + command.strip! + out, err, _st = Open3.capture3(command) + warn " RuCaptcha #{err.strip}" if err.present? + out + end + + # Generate a simple captcha image for Windows Platform + def convert_for_windows(size, code) + png_file_path = Rails.root.join('tmp', 'cache', "#{code}.png") + command = "convert -size #{size} xc:White -gravity Center -weight 12 -pointsize 20 -annotate 0 \"#{code}\" -trim #{png_file_path}" + _out, err, _st = Open3.capture3(command) + warn " RuCaptcha #{err.strip}" if err.present? + png_file_path + end + + # Geneate a uniq rgba colors for each chars + def uniq_rgbs_for_each_chars(chars) rgbs = [] chars.count.times do |i| color = random_color @@ -56,43 +109,11 @@ module RuCaptcha end rgbs << color end + rgbs + end - chars.each_with_index do |char, i| - rgb = RuCaptcha.config.style == :colorful ? rgbs[i] : rgbs[0] - text_color = "rgba(#{rgb.join(',')}, 1)" - line_color = "rgba(#{rgb.join(',')}, 0.6)" - text_opts << %(-fill '#{text_color}' -draw 'text #{(text_left + text_width) * i + all_left},#{text_top} "#{char}"') - left_y = rand_line_top(text_top, font_size) - right_x = half_width + (half_width * 0.3).to_i - right_y = rand_line_top(text_top, font_size) - line_opts << %(-draw 'stroke #{line_color} line #{rand(10)},#{left_y} #{right_x},#{right_y}') - end - - command = <<-CODE - convert -size #{size} \ - -strokewidth #{stroke_width} \ - #{line_opts.join(' ')} \ - -pointsize #{font_size} -weight 500 \ - #{text_opts.join(' ')} \ - -wave #{rand(2) + 3}x#{rand(2) + 1} \ - -rotate #{rand(10) - 5} \ - -gravity NorthWest -sketch 1x10+#{rand(2)} \ - -fill none \ - -implode #{RuCaptcha.config.implode} -trim label:- png:- - CODE - - if Gem.win_platform? - png_file_path = Rails.root.join('tmp', 'cache', "#{code}.png") - command = "convert -size #{size} xc:White -gravity Center -weight 12 -pointsize 20 -annotate 0 \"#{code}\" -trim #{png_file_path}" - out, err, _st = Open3.capture3(command) - warn " RuCaptcha #{err.strip}" if err.present? - png_file_path - else - command.strip! - out, err, _st = Open3.capture3(command) - warn " RuCaptcha #{err.strip}" if err.present? - out - end + def rand_line_top(text_top, font_size) + text_top + rand(font_size * 0.7).to_i end def warn(msg) diff --git a/spec/captcha_spec.rb b/spec/captcha_spec.rb index f2412a8..e594bec 100644 --- a/spec/captcha_spec.rb +++ b/spec/captcha_spec.rb @@ -34,4 +34,31 @@ describe RuCaptcha::Captcha do expect(colors).not_to eq colors1 end end + + describe '.rand_line_top' do + it 'should work' do + expect(RuCaptcha::Captcha.send(:rand_line_top, 1, 24)).to be_a(Integer) + end + end + + describe '.uniq_rgbs_for_each_chars' do + let(:chars) { %w(a b c d e) } + let(:colors) { RuCaptcha::Captcha.send(:uniq_rgbs_for_each_chars, chars) } + + it 'should work' do + expect(colors.length).to eq chars.length + expect(colors[0].length).to eq 3 + end + + it 'Be sure the color not same as preview color' do + pre_rgb = nil + colors.each do |rgb| + if pre_rgb + same = rgb.index(rgb.min) == pre_rgb.index(rgb.min) && rgb.index(rgb.max) == pre_rgb.index(pre_rgb.max) + expect(same).not_to eq true + end + pre_rgb = rgb + end + end + end end