Commits

Anonymous committed 5c625e0

First import of the tw2.captcha plugin

  • Participants
  • Parent commits f9b0f3e

Comments (0)

Files changed (26)

+include README.rst
+recursive-include tw2/captcha/templates *
+recursive-include tw2/captcha/static *
+from setuptools import setup, find_packages
+
+f = open('README.rst')
+long_description = f.read().strip()
+long_description = long_description.split('split here', 1)[1]
+f.close()
+
+install_requires=[
+    "tw2.core",
+]
+
+import sys
+if sys.version_info[0] == 2 and sys.version_info[1] < 7:
+    install_requires.extend([
+        "ordereddict",
+    ])
+
+setup(
+    name='tw2.captcha',
+    version='0.0.1',
+    description='toscawidgets2 captcha plugin',
+    long_description=long_description,
+    author='Pierre-Yves Chibon',
+    author_email='pingou@pingoured.fr',
+    url='http://github.com/pypingou/tw2.captcha',
+    install_requires=[
+        "tw2.core",
+        "pycrypto",
+        "PIL",
+    ],
+    packages=find_packages(exclude=['ez_setup']),
+    namespace_packages = ['tw2'],
+    zip_safe=False,
+    include_package_data=True,
+    entry_points="""
+        [tw2.widgets]
+            # Register your widgets so they can be listed in the WidgetBrowser
+            tw2.captcha = tw2.captcha
+        [tw2.captcha.jpeg_generators]
+            mcdermott = tw2.captcha.plugins.image.mcdermott:generate_jpeg
+            vanasco_dowty = tw2.captcha.plugins.image.vanasco_dowty:generate_jpeg
+            fred = tw2.captcha.plugins.image.fred:generate_jpeg
+        [tw2.captcha.text_generators]
+            random_ascii = tw2.captcha.plugins.text.random_ascii:generate_text
+            random_equation = tw2.captcha.plugins.text.random_equation:generate_text
+            fivelettername = tw2.captcha.plugins.text.fivelettername:generate_text
+    """,
+    keywords = [
+        'toscawidgets.widgets',
+    ],
+    classifiers = [
+        'Development Status :: 3 - Alpha',
+        'Environment :: Web Environment',
+        'Environment :: Web Environment :: ToscaWidgets',
+        'Topic :: Software Development :: Libraries :: Python Modules',
+        'Topic :: Software Development :: Widget Sets',
+        'Intended Audience :: Developers',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+    ],
+)

File tw2/__init__.py

+__import__('pkg_resources').declare_namespace(__name__)

File tw2/captcha/.gitignore

+dev/
+*.pyc
+*~
+

File tw2/captcha/__init__.py

+"""
+A captcha w2 widgets.
+
+Get this source from http://github.com/pypingou/tw2.captcha
+"""
+
+from widgets import (
+    Captcha,
+)

File tw2/captcha/model.py

+from datetime import datetime
+import calendar
+import cPickle
+
+
+class Captcha(object):
+    """Pertinent data about a Captcha.
+    
+    Exposed properties are:
+    plaintext: (read/write) a string representing the text of the captcha 
+                (i.e. what is it supposed to say)
+    created: (read only) the UTC date when the captcha was created. This 
+                data is updated when the plaintext property is updated.
+                
+    Exposed methods:
+    serialize(): returns a binary representation of the object
+    deseralize(obj): creates a Captcha object given the output of the
+                serialize() method. This is a classmethod.
+    """
+
+    _plaintext = None
+    _created = None  # stored as UTC
+
+    def __init__(self, plaintext=''):
+        super(Captcha, self).__init__()
+        self.plaintext = plaintext
+        self.label = None
+
+    def get_plaintext(self):
+        return self._plaintext
+
+    def set_plaintext(self, text):
+        self._plaintext = text
+        self._created =  datetime.utcnow()
+
+    plaintext = property(get_plaintext, set_plaintext)
+    # def get_created(self):
+    #     return self._created
+
+    c = lambda s: s._created
+
+    created = property(lambda s: s._created)
+
+    def serialize(self):
+        """Get a serialized binary representation of the object."""
+        # Serializing to a tuple containing the data elements instead of 
+        # just pickling the object is being done because the tuple 
+        # pickle is much smaller than the pickled object itself.
+        secs = int(calendar.timegm(self.created.utctimetuple()))
+        t = (self.plaintext, secs, self.label)
+        return cPickle.dumps(t, cPickle.HIGHEST_PROTOCOL)
+
+    def deserialize(cls, serialized_obj):
+        "Create a new Captcha object given output from the serialize method."
+        t = cPickle.loads(serialized_obj)
+        scp = cls()
+        scp._plaintext = t[0]
+        scp._created = datetime.utcfromtimestamp(t[1])
+        scp.label = t[2]
+        return scp
+    deserialize = classmethod(deserialize)

File tw2/captcha/plugins/__init__.py

Empty file added.

File tw2/captcha/plugins/image/__init__.py

Empty file added.

File tw2/captcha/plugins/image/fred.py

+import Image, ImageDraw, ImageFont, ImageFilter
+import random
+import os
+
+from pkg_resources import resource_filename
+from tw2.captcha.widgets import Captcha
+
+# get the font path 
+font_path = Captcha.text_font_path
+assert os.path.exists(font_path), \
+       'The font_path "%s" does not exist' % (font_path,)
+
+font_size = Captcha.text_font_size_min
+
+def generate_jpeg(text, file_obj):
+    # Settings ----------------------------------------------------
+    rand = random.randint
+    charNum = len(text)
+    charimg_w = font_size + 8
+    charimg_h = font_size + 8
+    img_w = (font_size * charNum) + 8
+    img_h = int(charimg_h * 1.5)
+    interval = font_size
+    lineNum = rand(5, 15)
+    font = ImageFont.truetype(font_path, font_size)
+    
+    # Create a background -----------------------------------------
+    bg_color = rand(0xbb, 0xee)
+    image = Image.new('RGB', (img_w, img_h), (bg_color, bg_color, bg_color))
+
+    # Generate text -----------------------------------------------
+    for i in range(0, charNum):
+        color = rand(0x111111, 0x444444)
+	# Create a small image to hold one character. Background is black
+	charImg = Image.new('RGB', (charimg_w, charimg_h), 0)
+	tmpDraw = ImageDraw.Draw(charImg)
+	# Draw text on this image
+	tmpDraw.text((3, 1), text[i], font=font, fill=color)
+	# Rotate a little bit, do some trick if you want
+	charImg = charImg.rotate(rand(-20,20))
+
+	# Create a mask which is same size of the small image
+	mask = Image.new('L', (charimg_w, charimg_h), 0)
+	mask.paste(charImg, (0, 0))
+
+	# Generate Random X Y
+	hpos = 8 + (i * interval) + rand(-8, 5)
+	vpos = rand(5, 15)
+
+	image.paste(charImg, (hpos, vpos), mask)
+	image.paste(charImg, (hpos+1, vpos+1), mask)
+        
+    image = image.filter(ImageFilter.SHARPEN)
+
+    # Draw few lines -----------------------------------------
+    draw = ImageDraw.Draw(image)
+    for i in range(0, lineNum):
+        draw.line((rand(1, img_w), rand(1, img_h),
+                   rand(1, img_w), rand(1, img_h)),
+                  fill=rand(0x666666, 0x999999)
+                  )
+        
+    image.save(file_obj, format='JPEG')
+
+if __name__ == "__main__":
+    generate_jpeg('hello', '/tmp/foo.jpg')
+    print "Captcha image generated."

File tw2/captcha/plugins/image/mcdermott.py

+import random
+import Image
+import ImageFont
+import ImageDraw
+import ImageFilter
+from pkg_resources import resource_filename
+import os.path
+from tw2.captcha.widgets import Captcha
+
+# get the font path
+font_path = Captcha.text_font_path
+assert os.path.exists(font_path), \
+        'The font_path "%s" does not exist' % (font_path,)
+
+text_font_size_min = Captcha.text_font_size_min
+bgcolor = Captcha.picture_bg_color
+fgcolor = Captcha.picture_fg_color
+# Code taken from
+# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440588
+# written by Robert McDermott
+
+def generate_jpeg(text, file_obj):
+    """Generate a captcha image"""
+    # randomly select the foreground color
+    fgcolor = random.randint(0,0xffff00)
+    # make the background color the opposite of fgcolor
+    bgcolor = fgcolor ^ 0xffffff
+    # create a font object 
+    font = ImageFont.truetype(font_path, text_font_size_min)
+    # determine dimensions of the text
+    dim = font.getsize(text)
+    # create a new image slightly larger that the text
+    im = Image.new('RGB', (dim[0]+5,dim[1]+5), bgcolor)
+    d = ImageDraw.Draw(im)
+    x, y = im.size
+    r = random.randint
+    # draw 100 random colored boxes on the background
+    for num in range(100):
+        d.rectangle((r(0,x),r(0,y),r(0,x),r(0,y)),fill=r(0, bgcolor ^ 0xffffff))
+    # add the text to the image
+    d.text((3,3), text, font=font, fill=fgcolor)
+    im = im.filter(ImageFilter.EDGE_ENHANCE_MORE)
+    # save the image to a file
+    im.save(file_obj, format='JPEG')

File tw2/captcha/plugins/image/vanasco_dowty/__init__.py

+import captcha
+import random
+import os.path
+from pkg_resources import resource_filename
+from tw2.captcha.widgets import Captcha
+
+width = Captcha.picture_width
+height = Captcha.picture_height
+bg_color = Captcha.picture_bg_color
+fg_color = Captcha.picture_fg_color
+font_size_min = Captcha.text_font_size_min
+font_size_max = Captcha.text_font_size_max
+
+captcha.font__paths = [Captcha.text_font_path]
+captcha.captcha__text__render_mode = Captcha.text_render_mode
+captcha.captcha__font_range = (font_size_min, font_size_max)
+
+
+def generate_jpeg(text, file_):
+    font_size = random.randint(font_size_min, font_size_max)
+    fg = random.choice(fg_color)
+    ci = captcha._Captcha__Img(text, width, height, font_size, fg, bg_color)
+    image = ci.render()
+    image.save(file_, format='JPEG')

File tw2/captcha/plugins/image/vanasco_dowty/captcha.py

+""" Captcha
+
+Classes to implement a Captcha system in TurboGears
+
+(c) 2006 jonathan vanasco <jvanasco@gmail.com>
+
+Based in part on 
+    "PyCAPTCHA Package Copyright (C) 2004 Micah Dowty <micah@navi.cx>" 
+        *   _PyCaptcha_ prefaced classes are directly from that library (with a 
+            few naming adjustments)
+        *   other items are influenced by it, including the 'layering' idea.  
+        Full Resource - http://svn.navi.cx/misc/trunk/pycaptcha/
+    "Human verification test (captcha) by Robert McDermott 2005/09/21"
+        Full Resource - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440588 
+        
+Licensing:
+
+Copyright (c) 2006 Jonathan Vanasco
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to 
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+of the Software, and to permit persons to whom the Software is furnished to do 
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all 
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
+SOFTWARE.
+
+The PyCaptcha Sections have licensing as follows:
+
+Copyright (c) 2004 Micah Dowty
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to 
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+of the Software, and to permit persons to whom the Software is furnished to do 
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all 
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
+SOFTWARE.
+
+"""
+
+try:
+    from hashlib import md5 as md5_constructor
+except ImportError:
+    from md5 import new as md5_constructor
+import random
+import math 
+import os
+import time
+
+import Image
+import ImageFont
+import ImageDraw
+import ImageFilter
+
+captcha__site_secret = ";lkjsadfiqwrmbasfyuvb"
+captcha__font_range = (30,45)
+captcha__text_length = 6
+captcha__img_width = 300
+captcha__img_height = 100
+captcha__img_color_bg = "#DDDDDD"
+captcha__img_color_fg = 'RANDOM'
+captcha__expiry_time = 300
+captcha__future_expiry_time = 30
+
+data_dir = os.path.join( os.path.split(os.path.abspath(__file__))[0], "captcha_data" )
+captcha__img_expired__text = """IMAGE EXPIRED"""
+captcha__img_expired__font = os.path.join( data_dir , 'fonts','vera','VeraBd.ttf')
+captcha__img_expired__fontsize = 14
+
+captcha__text__render_mode = 'by_letter' # ( by_letter | whole_word )
+
+font__paths = []
+# _font_path = os.path.join(data_dir, 'fonts')
+# for item in os.listdir(_font_path):
+#     if item == '.svn':
+#         continue
+#     item_path = os.path.join(_font_path,item)
+#     if os.path.isdir(item_path):
+#         for _font in os.listdir(item_path):
+#             if _font.endswith('.ttf'):
+#                 font__paths.append( os.path.join(item_path,_font) )
+
+class _ErrorLoggingObject(object):
+    def get_error( self , function ):
+        if function not in self._errors:
+            return None
+        return self._errors[function]
+    
+    def set_error( self , function , error ):
+        self._errors[function] = error
+
+class _Captcha(_ErrorLoggingObject) :
+    """Captcha implementation for TurboGears"""
+    def __init__( self  , captcha_seed=None ):
+        """initialize the captcha"""
+        self.captcha_text = None
+        self.captcha_seed = captcha_seed
+        self._errors = {}
+    
+    def generate_key(self):
+        """Generates a key that can be used to ( generate a captcha ) or ( validate a captcha )"""
+        self.captcha_time_start = int(time.time())
+        self.captcha_key = self._generate_key( captcha_time_start=self.captcha_time_start , captcha_seed=self.captcha_seed )
+        self._captcha_key__combine()
+
+    def _generate_key( self , captcha_time_start=None , captcha_seed=None ):
+        """Returns a hash based on text , seed , and site_secrect"""
+        return md5_constructor("%s|%s|%s" %(captcha__site_secret,captcha_time_start,captcha_seed)).hexdigest()
+
+    def generate_captcha_text(self):
+        """Automagically generates a string of text based on a key (length is from file default or override)"""
+        return ''.join(md5_constructor("%s|%s|%s" %(captcha__site_secret,self.captcha_key,self.captcha_time_start)).hexdigest()[0:6])
+
+    def _captcha_key__uncombine( self ):
+        ( self.captcha_key , self.captcha_time_start ) = self.captcha_key_combined.split('_')
+        self.captcha_time_start = int(self.captcha_time_start)
+
+    def _captcha_key__combine( self ):
+        self.captcha_key_combined = '_'.join( [ self.captcha_key , "%s"%self.captcha_time_start ] )
+
+
+
+class CaptchaNew( _Captcha ):
+    def __init__( self , captcha_seed=None , text_length=captcha__text_length ):
+        _Captcha.__init__( self , captcha_seed=None )
+
+
+class CaptchaExisting( _Captcha ):
+    def __init__( self , captcha_seed=None , captcha_key_combined=None , img_width=captcha__img_width , img_height=captcha__img_height , img_color_bg=captcha__img_color_bg , img_color_fg=captcha__img_color_fg ):
+        _Captcha.__init__( self , captcha_seed=None )
+        if captcha_key_combined is None:
+            raise ValueError( "captcha_key_combined must be 'key_timestart'")
+        self.captcha_key_combined = captcha_key_combined
+        self._captcha_key__uncombine()
+        self.img_width = img_width
+        self.img_height = img_height
+        self.img_color_bg = img_color_bg
+        self.img_color_fg = img_color_fg
+        self.time_now = int(time.time())
+
+    def is_timely( self ):
+    
+        # is the captcha too old?
+        if self.time_now > ( self.captcha_time_start + captcha__expiry_time) :
+            self.set_error('is_timely','EXPIRED captcha time')
+            return 0
+
+        # is the captcha too new?
+        if self.captcha_time_start > ( self.time_now + captcha__future_expiry_time ) :
+            self.set_error('is_timely','FUTURE captcha time')
+            return 0
+
+        return 1
+
+    def validate( self , user_text=None ):
+        """Validates a text against the key/time"""
+        
+        self.success = False
+        
+        if not self.is_timely() :
+            self.set_error('validate',self.get_error('is_timely'))
+            return 0
+        
+        if user_text == self.generate_captcha_text():
+            self.success = True
+            return 1
+
+        self.set_error('validate',"INVALID user_text")
+        return 0
+
+
+    def generate_image( self ):
+        """Generate a captcha image"""
+        
+        
+        t_start = time.time()
+        
+        captcha_text = self.captcha_text
+        if captcha_text is None:  
+            captcha_text = self.generate_captcha_text()
+
+        img_color_bg = self.img_color_bg
+        img_color_fg = self.img_color_fg
+        if img_color_fg == 'RANDOM':
+            img_color_fg = random.choice(["#330000","#660000","#003300","#006600","#000033","#000066"])
+
+        img = None
+        if not self.is_timely() :
+            img = _Captcha__ImgExpired( width=self.img_width , height=self.img_height , color_bg=img_color_bg )
+        else:
+           img = _Captcha__Img( 
+                text = captcha_text,
+                width=self.img_width,
+                height=self.img_height,
+                font_size = random.randint(captcha__font_range[0],captcha__font_range[1]),
+                color_fg = img_color_fg,
+                color_bg = img_color_bg
+            )
+        img.render()
+        #print "Time To Render: %s " % ( time.time() - t_start )
+        self.img = img
+        return self
+
+    def render_img( self ):
+        return self.img.getImg().tostring( "jpeg" , "RGB" )
+
+    def save(self):
+        self.img.getImg().save('b.jpg')
+        return True
+
+
+class _Captcha__ImgExpired:
+    def __init__( self, width=captcha__img_width , height=captcha__img_height , color_bg="#FFFFFF"):
+        self.width = width
+        self.height = height
+        self.color_bg = color_bg
+        self.color_fg = "#000000"
+
+    def getImg(self):
+        """Get a PIL image representing this IMG test, creating it if necessary"""
+        if not self._image:
+            self._image = self.render()
+        return self._image
+
+    def render(self):
+        """Render this CAPTCHA, returning a PIL image"""
+        size = (self.width,self.height)
+        img = Image.new("RGB", size )
+        
+        #first the bg
+        img.paste( self.color_bg )
+
+        #then the text
+        font = ImageFont.truetype( captcha__img_expired__font , captcha__img_expired__fontsize )
+        text_dimensions = font.getsize(captcha__img_expired__text)
+
+        draw = ImageDraw.Draw(img)
+        draw.text( 
+            (
+                ((self.width - text_dimensions[0])/2) ,
+                ((self.height - text_dimensions[1])/2) 
+            ) , 
+            captcha__img_expired__text, font=font, fill=self.color_fg
+        )
+        self._image = img
+        return self._image
+        
+        
+class _Captcha__Img:
+    def __init__( self, text="Error! No Text Supplied" , width=captcha__img_width , height=captcha__img_height , font_size = random.randint(captcha__font_range[0],captcha__font_range[1]) , color_fg="#000000" , color_bg="#FFFFFF"):
+        self.text = text
+        self.width = width
+        self.height = height
+        self.font_size = font_size
+        self.color_fg = color_fg
+        self.color_bg = color_bg
+        self._layers = [
+            _Captcha__bg( color=self.color_bg ),
+            _Captcha__Img__text( text=self.text , font_size=self.font_size, color=self.color_fg , canvas_width=width , canvas_height=height),
+            _Captcha__Img__lines(color=self.color_fg , canvas_width=width , canvas_height=height),
+            _PyCaptcha_SineWarp(amplitudeRange = (4, 8) , periodRange=(0.65,0.73) ),
+        ]
+        
+    def getImg(self):
+        """Get a PIL image representing this CAPTCHA test, creating it if necessary"""
+        if not self._image:
+            self._image = self.render()
+        return self._image
+
+    def render(self):
+        """Render this CAPTCHA, returning a PIL image"""
+        size = (self.width,self.height)
+        img = Image.new("RGB", size )
+        for layer in self._layers:
+            img = layer.render( img ) or img
+        self._image = img
+        return self._image
+        
+
+
+class _Captcha__bg( _Captcha__Img ):
+    """BG class for image CAPTCHA tests."""
+    def __init__(self, color="#ffffff"):
+        self.color = color
+
+    def render(self, image ):
+        # lt grey bg - just a design choice
+        image.paste( self.color )
+
+class _Captcha__Img__text( _Captcha__Img ):
+    """Text class for image CAPTCHA tests."""
+    def __init__( self , text="Error! No Text Supplied" , font_size=10, color="#000000" , canvas_width=captcha__img_width , canvas_height=captcha__img_height):
+        self.text = text
+        self.font_size = font_size
+        self.color = color
+        self.canvas_width = canvas_width
+        self.canvas_height = canvas_height
+
+    def render( self , img ):
+        if captcha__text__render_mode == 'by_letter':
+            self.render__by_letter( img )
+        else:
+            self.render__whole_word( img )
+
+    def render__by_letter(self, img):
+        """Renders text onto the img"""
+        # pick a random font and size it, then x1.5 in case its small
+        font = ImageFont.truetype(  *_PyCaptcha_FontFactory().pick() )
+        text_dimensions = [ int(1.2 * i) for i in font.getsize(self.text) ]
+        letter_width = text_dimensions[0] / len(self.text)
+        
+        startX = int( random.randint(5,(self.canvas_width - text_dimensions[0]-5)) )
+        startY = int( random.randint(5,(self.canvas_height - text_dimensions[1]-5)) )
+
+        draw = ImageDraw.Draw(img)
+        for letter_index in range( 0 , len(self.text)):
+            draw.text( 
+                (
+                    (startX + (letter_index * letter_width )),
+                    (startY + ( random.randint(-10,10) )),
+                ),
+                self.text[letter_index],
+                font = ImageFont.truetype(  *_PyCaptcha_FontFactory().pick() ),
+                fill = self.color
+            )
+        
+        
+    def render__whole_word(self, img):
+        """Renders text onto the img"""
+        font = ImageFont.truetype(  *_PyCaptcha_FontFactory().pick() )
+        text_dimensions = font.getsize(self.text)
+
+        draw = ImageDraw.Draw(img)
+
+        r = random.randint
+        draw.text( 
+            (
+                r(5,(self.canvas_width - text_dimensions[0]-5)) , 
+                r(5,(self.canvas_height - text_dimensions[1]-5)) 
+            ), 
+            self.text, font=font, fill=self.color
+        )
+
+class _Captcha__Img__lines( _Captcha__Img ):
+    def __init__( self , color="#000000" , canvas_width=captcha__img_width , canvas_height=captcha__img_height):
+        self.color = color
+        self.canvas_width = canvas_width
+        self.canvas_height = canvas_height
+
+    def render(self, img):
+        """Renders lines onto the img"""
+
+        draw = ImageDraw.Draw(img)
+
+        # some sweeping arcs
+        for i in range( 1 , random.randint(1,4) ):
+            ( a1 , a2 ) = ( random.randint(0,360) , random.randint(0,360) )
+            ( bbox1 , bbox2 , bbox3 , bbox4 ) = (
+                random.randint((2*-self.canvas_width),self.canvas_width),
+                random.randint((2*-self.canvas_height),self.canvas_height),
+                random.randint((.5*self.canvas_width),(2*self.canvas_width)),
+                random.randint((.5*self.canvas_height),(2*self.canvas_height))
+            )
+            draw.arc( ( bbox1 , bbox2 , bbox3 , bbox4 ) , a1 , a2,fill=self.color) 
+            #randomly we'll draw some lines thicker.  this is accomplished by shifting the bounding box a bit in each direction
+            if random.randint(0,100) > 50:
+                #draw.arc( ( bbox1+1 , bbox2+1 , bbox3 , bbox4 ) , a1 , a2,fill=self.color) 
+                #draw.arc( ( bbox1 , bbox2 , bbox3-1 , bbox4-1 ) , a1 , a2,fill=self.color) 
+                # random.randint is out thickness
+                for i in range (1 , random.randint(2,3) ):
+                    draw.arc( ( bbox1+i , bbox2+i , bbox3 , bbox4 ) , a1 , a2,fill=self.color) 
+                    draw.arc( ( bbox1 , bbox2 , bbox3+i , bbox4+i ) , a1 , a2,fill=self.color) 
+                
+        # little arcs
+        for i in range( 1 , random.randint(5,15) ):
+            ( a1 , a2 ) = ( random.randint(0,360) , random.randint(0,360) )
+            ( bbox1 , bbox2 ) = (
+                random.randint(-20,(1.25*self.canvas_width)),
+                random.randint(-20,(1.25*self.canvas_height)),
+            )
+            ( bbox3 , bbox4 ) = (
+                bbox1 + random.randint(0,40),
+                bbox2 + random.randint(0,40)
+            )
+            draw.arc( ( bbox1 , bbox2 , bbox3 , bbox4 ) , a1 , a2,fill=self.color) 
+            #randomly we'll draw some lines thicker.  this is accomplished by shifting the bounding box a bit in each direction
+            if random.randint(0,100) > 50:
+                #draw.arc( ( bbox1+1 , bbox2+1 , bbox3 , bbox4 ) , a1 , a2,fill=self.color) 
+                #draw.arc( ( bbox1 , bbox2 , bbox3-1 , bbox4-1 ) , a1 , a2,fill=self.color) 
+                # random.randint is out thickness
+                for i in range (1 , random.randint(2,3) ):
+                    draw.arc( ( bbox1+i , bbox2 , bbox3+i , bbox4 ) , a1 , a2,fill=self.color) 
+                    draw.arc( ( bbox1-i , bbox2 , bbox3-i , bbox4 ) , a1 , a2,fill=self.color) 
+         
+        # and a few lines
+        for i in range( 1 , random.randint(5,15) ):
+            ( bbox1 , bbox2 ) = (
+                random.randint(-20,(1.25*self.canvas_width)),
+                random.randint(-20,(1.25*self.canvas_height)),
+            )
+            ( bbox3 , bbox4 ) = (
+                bbox1 + random.randint(0,40),
+                bbox2 + random.randint(0,40)
+            )
+            draw.line( ( bbox1 , bbox2 , bbox3 , bbox4 ) ,fill=self.color) 
+            #randomly we'll draw some lines thicker.  this is accomplished by shifting the bounding box a bit in each direction
+            if random.randint(0,100) > 50:
+                for i in range (1 , random.randint(3,6) ):
+                    draw.line( ( bbox1+i , bbox2+i , bbox3 , bbox4 ) , fill=self.color) 
+                    draw.line( ( bbox1 , bbox2 , bbox3+i , bbox4+i ) , fill=self.color) 
+
+
+class _PyCaptcha_WarpBase(object):
+    """Abstract base class for image warping. Subclasses define a
+       function that maps points in the output image to points in the input image.
+       This warping engine runs a grid of points through this transform and uses
+       PIL's mesh transform to warp the image.
+       """
+    filtering = Image.BILINEAR
+    resolution = 10
+
+    def get_transform(self, image):
+        """Return a transformation function, subclasses should override this"""
+        return lambda x, y: (x, y)
+
+    def render(self, image):
+        r = self.resolution
+        xPoints = image.size[0] / r + 2
+        yPoints = image.size[1] / r + 2
+        f = self.get_transform(image)
+
+        # Create a list of arrays with transformed points
+        xRows = []
+        yRows = []
+        for j in xrange(yPoints):
+            xRow = []
+            yRow = []
+            for i in xrange(xPoints):
+                x, y = f(i*r, j*r)
+
+                # Clamp the edges so we don't get black undefined areas
+                x = max(0, min(image.size[0]-1, x))
+                y = max(0, min(image.size[1]-1, y))
+
+                xRow.append(x)
+                yRow.append(y)
+            xRows.append(xRow)
+            yRows.append(yRow)
+
+        # Create the mesh list, with a transformation for
+        # each square between points on the grid
+        mesh = []
+        for j in xrange(yPoints-1):
+            for i in xrange(xPoints-1):
+                mesh.append((
+                    # Destination rectangle
+                    (i*r, j*r,
+                     (i+1)*r, (j+1)*r),
+                    # Source quadrilateral
+                    (xRows[j  ][i  ], yRows[j  ][i  ],
+                     xRows[j+1][i  ], yRows[j+1][i  ],
+                     xRows[j+1][i+1], yRows[j+1][i+1],
+                     xRows[j  ][i+1], yRows[j  ][i+1]),
+                    ))
+
+        return image.transform(image.size, Image.MESH, mesh, self.filtering)
+
+
+class _PyCaptcha_SineWarp(_PyCaptcha_WarpBase):
+    """Warp the image using a random composition of sine waves"""
+
+    def __init__(self,
+                 amplitudeRange = (4, 20),
+                 periodRange    = (0.65, 0.74),
+                 ):
+        self.amplitude = random.uniform(*amplitudeRange)
+        self.period = random.uniform(*periodRange)
+        self.offset = (random.uniform(0, math.pi * 2 / self.period),
+                       random.uniform(0, math.pi * 2 / self.period))
+                       
+    def get_transform(self, image):
+        return (lambda x, y,
+                a = self.amplitude,
+                p = self.period,
+                o = self.offset:
+                (math.sin( (y+o[0])*p )*a + x,
+                 math.sin( (x+o[1])*p )*a + y))    
+
+class _PyCaptcha_FontFactory(object):
+    """Picks random fonts and/or sizes from a given list.
+       'sizes' can be a single size or a (min,max) tuple.
+       If any of the given files are directories, all *.ttf found
+       in that directory will be added.
+       """
+
+    def _pick_file(self):
+        try:
+            return random.choice(font__paths)
+        except:
+            return captcha__img_expired__font
+
+    def pick(self):
+        """Returns a (fileName, size) tuple that can be passed to ImageFont.truetype()"""
+        fileName = self._pick_file()
+        size = int(random.uniform(captcha__font_range[0], captcha__font_range[1]) + 0.5)
+        return (fileName, size)

File tw2/captcha/plugins/text/__init__.py

Empty file added.

File tw2/captcha/plugins/text/fivelettername.py

+"""Generate a pseudo-real five letter name."""
+
+import random
+
+def generate_text():
+    return random.choice(names)
+
+# Names taken from the simpleaptcha project:
+# http://simplecaptcha.sourceforge.net/
+
+# Also avaliable in the kaptcha project
+# http://code.google.com/p/kaptcha/
+# Which is available under the Apache License 2.0
+
+# Original source??
+# http://people.scs.fsu.edu/~burkardt/fun/wordplay/pentagram.html
+
+names = [
+        "jomah",
+		"yanae",
+		"dagna",
+		"aadel",
+		"aaden",
+		"aafia",
+		"aamir",
+		"aaran",
+		"aaren",
+		"aaric",
+		"aarin",
+		"aarle",
+		"aaron",
+		"aarti",
+		"aaryn",
+		"aasia",
+		"aasif",
+		"aasim",
+		"abame",
+		"abani",
+		"abasi",
+		"abbah",
+		"abban",
+		"abbia",
+		"abbie",
+		"abbud",
+		"abbye",
+		"abdel",
+		"abdou",
+		"abdul",
+		"abdur",
+		"abdus",
+		"abebe",
+		"abebi",
+		"abeer",
+		"abeli",
+		"abena",
+		"abeni",
+		"abhay",
+		"abias",
+		"abida",
+		"abiel",
+		"abija",
+		"abina",
+		"abira",
+		"abnar",
+		"abner",
+		"abnor",
+		"aboor",
+		"abott",
+		"abram",
+		"abran",
+		"abree",
+		"abril",
+		"abuid",
+		"aceng",
+		"achal",
+		"achaz",
+		"achim",
+		"achin",
+		"acima",
+		"ackim",
+		"acree",
+		"adala",
+		"adali",
+		"adaly",
+		"adama",
+		"adamo",
+		"adamu",
+		"adara",
+		"addal",
+		"addam",
+		"addar",
+		"addey",
+		"addia",
+		"addie",
+		"addis",
+		"adeen",
+		"adela",
+		"adele",
+		"adell",
+		"adena",
+		"adene",
+		"adham",
+		"adiah",
+		"adiba",
+		"adiel",
+		"adika",
+		"adila",
+		"adima",
+		"adina",
+		"adine",
+		"adira",
+		"aditi",
+		"adiva",
+		"adjoa",
+		"adlai",
+		"adlan",
+		"adlar",
+		"adler",
+		"adley",
+		"adlin",
+		"adlyn",
+		"admad",
+		"admir",
+		"admon",
+		"adnah",
+		"adnan",
+		"adnet",
+		"adney",
+		"adnot",
+		"adola",
+		"adolf",
+		"adoum",
+		"adrea",
+		"adrie",
+		"adrik",
+		"adron",
+		"aemon",
+		"aeric",
+		"aerin",
+		"aesha",
+		"afemo",
+		"afeni",
+		"affan",
+		"affra",
+		"afida",
+		"afsar",
+		"aftan",
+		"aftyn",
+		"agace",
+		"agafi",
+		"agapi",
+		"agata",
+		"aggye",
+		"aghar",
+		"aghna",
+		"agias",
+		"agnar",
+		"agota",
+		"agote",
+		"agoti",
+		"agron",
+		"ahanu",
+		"ahava",
+		"ahdaf",
+		"ahern",
+		"ahiva",
+		"ahlam",
+		"ahley",
+		"ahmad",
+		"ahman",
+		"ahmed",
+		"ahmet",
+		"ahran",
+		"ahren",
+		"ahrin",
+		"ahsan",
+		"ahuda",
+		"ahuva",
+		"ahvie",
+		"aidah",
+		"aidan",
+		"aiden",
+		"aidya",
+		"aiken",
+		"aikin",
+		"ailee",
+		"ailey",
+		"ailie",
+		"ailin",
+		"ailis",
+		"ailli",
+		"ailsa",
+		"ailse",
+		"aimal",
+		"aimee",
+		"aimey",
+		"aimia",
+		"aimie",
+		"aimil",
+		"aimon",
+		"ainah",
+		"ainka",
+		"ainur",
+		"airat",
+		"airel",
+		"airic",
+		"airle",
+		"aisha",
+		"aisia",
+		"aitan",
+		"aitor",
+		"aizik",
+		"ajaib",
+		"ajali",
+		"ajani",
+		"ajaya",
+		"ajeya",
+		"ajhon",
+		"ajith",
+		"ajmal",
+		"ajsha",
+		"akako",
+		"akara",
+		"akash",
+		"akati",
+		"akbar",
+		"akeam",
+		"akeel",
+		"akeem",
+		"akemi",
+		"akhil",
+		"akiba",
+		"akiel",
+		"akiem",
+		"akiko",
+		"akila",
+		"akili",
+		"akima",
+		"akins",
+		"akira",
+		"akiva",
+		"akiyo",
+		"akmal",
+		"akono",
+		"akram",
+		"aksel",
+		"alade",
+		"alaen",
+		"alaia",
+		"alain",
+		"alair",
+		"alaka",
+		"alana",
+		"alani",
+		"alann",
+		"alano",
+		"alard",
+		"alayn",
+		"alban",
+		"alben",
+		"alber",
+		"albie",
+		"albis",
+		"alcot",
+		"aldas",
+		"aldea",
+		"alden",
+		"aldie",
+		"aldin",
+		"aldis",
+		"aldon",
+		"aldus",
+		"aldya",
+		"aleah",
+		"aleco",
+		"aleda",
+		"aleea",
+		"aleen",
+		"aleia",
+		"aleix",
+		"alejo",
+		"aleka",
+		"aleki",
+		"aleks",
+		"alena",
+		"alene",
+		"aleni",
+		"aleta",
+		"alexa",
+		"alexi",
+		"alexx",
+		"alexy",
+		"aleya",
+		"alfeo",
+		"alfie",
+		"alfio",
+		"alfre",
+		"alfri",
+		"algar",
+		"algey",
+		"algie",
+		"algis",
+		"algon",
+		"alica",
+		"alice",
+		"alick",
+		"alida",
+		"alies",
+		"alija",
+		"alika",
+		"alina",
+		"alisa",
+		"alise",
+		"alisz",
+		"alita",
+		"alixe",
+		"aliye",
+		"aliza",
+		"alize",
+		"alkis",
+		"alkot",
+		"allam",
+		"allan",
+		"allax",
+		"allda",
+		"allea",
+		"allen",
+		"alles",
+		"allex",
+		"allfy",
+		"allie",
+		"allin",
+		"allix",
+		"allma",
+		"allon",
+		"allta",
+		"allyn",
+		"allys",
+		"almaz",
+		"almil",
+		"almir",
+		"alois",
+		"alojz",
+		"aloke",
+		"alona",
+		"aloni",
+		"aloys",
+		"alphy",
+		"alpin",
+		"alred",
+		"alric",
+		"alrik",
+		"alroy",
+		"altaf",
+		"alten",
+		"aluin",
+		"aluki",
+		"aluna",
+		"alura",
+		"aluse",
+		"alvah",
+		"alvan",
+		"alven",
+		"alves",
+		"alvie",
+		"alvin",
+		"alvis",
+		"alvyn",
+		"alvys",
+		"alwan",
+		"alwin",
+		"alwyn",
+		"alyce",
+		"alyda",
+		"alyma",
+		"alyna",
+		"alyne",
+		"alynn",
+		"alysa",
+		"alyse",
+		"alyso",
+		"alyss",
+		"amada",
+		"amado",
+		"amadu",
+		"amahd",
+		"amahl",
+		"amaka",
+		"amaly",
+		"amare",
+		"amary",
+		"amasa",
+		"amata",
+		"amato",
+		"amaui",
+		"amaya",
+		"ambie",
+		"ambra",
+		"ambur",
+		"amela",
+		"amena",
+		"amery",
+		"amiah",
+		"amica",
+		"amiel",
+		"amiet",
+		"amijo",
+		"amiko",
+		"amina",
+		"amira",
+		"amirh",
+		"amiri",
+		"amisa",
+		"amita",
+		"amjad",
+		"ammar",
+		"ammer",
+		"ammie",
+		"amneh",
+		"amnon",
+		"amona",
+		"amory",
+		"amrit",
+		"amyas",
+		"amyot",
+		"amyra",
+		"amzie",
+		"anais",
+		"anaka",
+		"anand",
+		"anant",
+		"ancel",
+		"andee",
+		"ander",
+		"andie",
+		"andis",
+		"andje",
+		"andon",
+		"andor",
+		"andra",
+		"andre",
+		"andri",
+		"andru",
+		"andry",
+		"aneel",
+		"aneka",
+		"anela",
+		"aneta",
+		"anete",
+		"anett",
+		"aneva",
+		"angee",
+		"angen",
+		"angey",
+		"angie",
+		"angil",
+		"aniah",
+		"anica",
+		"anice",
+		"anida",
+		"aniel",
+		"anier",
+		"anika",
+		"aniko",
+		"anina",
+		"anisa",
+		"anita",
+		"aniya",
+		"anjel",
+		"anjil",
+		"ankie",
+		"annee",
+		"annes",
+		"anney",
+		"annia",
+		"annie",
+		"annik",
+		"annis",
+		"annot",
+		"annus",
+		"annys",
+		"annza",
+		"anoki",
+		"anoop",
+		"anora",
+		"anouk",
+		"anour",
+		"ansam",
+		"ansel",
+		"ansly",
+		"anson",
+		"ansor",
+		"anssi",
+		"ansun",
+		"antaw",
+		"antha",
+		"anthe",
+		"antia",
+		"antin",
+		"antje",
+		"anton",
+		"antos",
+		"antti",
+		"anuja",
+		"anwar",
+		"anwel",
+		"anwen",
+		"anwil",
+		"anwyl",
+		"anwyn",
+		"anyes",
+		"aoife",
+		"aphra",
+		"apolo",
+		"aponi",
+		"apryl",
+		"aquil",
+		"aralt",
+		"arash",
+		"arata",
+		"arati",
+		"arbel",
+		"arben",
+		"archy",
+		"ardah",
+		"ardal",
+		"ardia",
+		"ardie",
+		"ardin",
+		"ardis",
+		"ardly",
+		"ardon",
+		"ardra",
+		"ardys",
+		"areck",
+		"areen",
+		"arela",
+		"areli",
+		"arese",
+		"areta",
+		"argeo",
+		"arial",
+		"arick",
+		"arics",
+		"arieh",
+		"arien",
+		"ariez",
+		"arija",
+		"arika",
+		"arild",
+		"arina",
+		"arinn",
+		"arisa",
+		"ariya",
+		"ariza",
+		"arize",
+		"arjen",
+		"arkan",
+		"arkin",
+		"arkyn",
+		"arlan",
+		"arlea",
+		"arlee",
+		"arlen",
+		"arlet",
+		"arley",
+		"arlie",
+		"arlin",
+		"arlis",
+		"arlyn",
+		"armad",
+		"arman",
+		"armin",
+		"armon",
+		"arnat",
+		"arndt",
+		"arnel",
+		"arnet",
+		"arney",
+		"arnie",
+		"arnis",
+		"arnny",
+		"arnoe",
+		"arnon",
+		"arnou",
+		"aroni",
+		"arren",
+		"arria",
+		"arric",
+		"arrik",
+		"arrio",
+		"arron",
+		"arryo",
+		"arsen",
+		"artek",
+		"artha",
+		"arthi",
+		"artie",
+		"artin",
+		"artis",
+		"artor",
+		"artur",
+		"artus",
+		"aruna",
+		"aruns",
+		"arvah",
+		"arvid",
+		"arvie",
+		"arvon",
+		"arwel",
+		"arwen",
+		"arwin",
+		"arwyn",
+		"aryeh",
+		"aryel",
+		"asaad",
+		"asafa",
+		"asano",
+		"asaph",
+		"aseel",
+		"ashah",
+		"ashar",
+		"ashby",
+		"ashia",
+		"ashis",
+		"ashla",
+		"ashli",
+		"ashly",
+		"ashon",
+		"ashor",
+		"asiah",
+		"asiak",
+		"asier",
+		"asifa",
+		"asiff",
+		"asima",
+		"asiri",
+		"asish",
+		"asius",
+		"aslak",
+		"aslan",
+		"aslin",
+		"asmaa",
+		"asmus",
+		"asoke",
+		"asoma",
+		"aspar",
+		"asril",
+		"asror",
+		"assad",
+		"assaf",
+		"assem",
+		"assia",
+		"assie",
+		"asten",
+		"astin",
+		"astra",
+		"astri",
+		"aswin",
+		"aswyn",
+		"atala",
+		"atara",
+		"atera",
+		"athan",
+		"athie",
+		"athol",
+		"athra",
+		"atida",
+		"atifa",
+		"atila",
+		"atima",
+		"atina",
+		"atiya",
+		"atlea",
+		"atley",
+		"atner",
+		"atrie",
+		"attie",
+		"atyaf",
+		"auban",
+		"auben",
+		"aubra",
+		"aubre",
+		"aubri",
+		"aubry",
+		"audon",
+		"audra",
+		"audre",
+		"audri",
+		"audry",
+		"audun",
+		"audun",
+		"auggy",
+		"augie",
+		"aukai",
+		"aulii",
+		"aurea",
+		"aurek",
+		"aurel",
+		"aurey",
+		"auria",
+		"aurie",
+		"ausaf",
+		"ausra",
+		"autry",
+		"autum",
+		"auvit",
+		"avada",
+		"avais",
+		"avari",
+		"avary",
+		"aveen",
+		"avent",
+		"avern",
+		"avery",
+		"avice",
+		"aviel",
+		"aviva",
+		"avivi",
+		"aviya",
+		"avner",
+		"avnit",
+		"avram",
+		"avrel",
+		"avril",
+		"avrim",
+		"avrit",
+		"avrom",
+		"avrum",
+		"avtar",
+		"awani",
+		"axell",
+		"axill",
+		"axton",
+		"ayaan",
+		"ayaaz",
+		"ayako",
+		"ayala",
+		"ayame",
+		"ayana",
+		"aydan",
+		"ayden",
+		"ayers",
+		"ayham",
+		"ayhan",
+		"ayisa",
+		"ayken",
+		"aykin",
+		"aylee",
+		"aylie",
+		"aymen",
+		"aymer",
+		"aymil",
+		"aymon",
+		"aynor",
+		"ayoka",
+		"ayrel",
+		"ayres",
+		"aysel",
+		"aysha",
+		"ayshe",
+		"aysia",
+		"ayson",
+		"aytac",
+		"aytza",
+		"ayubu",
+		"ayumi",
+		"ayzan",
+		"azael",
+		"azami",
+		"azaya",
+		"azhar",
+		"azibo",
+		"azita",
+		"aziza",
+		"azize",
+		"azizi",
+		"azmat",
+		"azora",
+		"azuba",
+		"azura",
+		"azwar",
+		"azzam",
+		"babak",
+		"babek",
+		"babet",
+		"babsy",
+		"badal",
+		"badih",
+		"badja",
+		"badra",
+		"badri",
+		"bagot",
+		"bahaa",
+		"baher",
+		"bahni",
+		"bahri",
+		"baila",
+		"baily",
+		"baird",
+		"bakri",
+		"balak",
+		"balal",
+		"balin",
+		"bamby",
+		"bandi",
+		"banky",
+		"bantz",
+		"baqir",
+		"barba",
+		"barbi",
+		"barby",
+		"barie",
+		"barna",
+		"barri",
+		"barrt",
+		"barta",
+		"barto",
+		"bartt",
+		"barty",
+		"bartz",
+		"barun",
+		"baryn",
+		"basak",
+		"basam",
+		"basem",
+		"basia",
+		"basir",
+		"basma",
+		"basob",
+		"basya",
+		"batia",
+		"batya",
+		"bauby",
+		"baxie",
+		"baxty",
+		"bayly",
+		"bayne",
+		"bayrd",
+		"bazek",
+		"bazel",
+		"bazil",
+		"beale",
+		"beall",
+		"beals",
+		"beann",
+		"beate",
+		"beaty",
+		"beavo",
+		"bebba",
+		"bebel",
+		"becca",
+		"becka",
+		"becki",
+		"becky",
+		"bejun",
+		"bekka",
+		"bekki",
+		"belal",
+		"belia",
+		"bella",
+		"bello",
+		"belva",
+		"beman",
+		"benay",
+		"benci",
+		"benek",
+		"bengt",
+		"benio",
+		"benja",
+		"benji",
+		"benke",
+		"benna",
+		"benno",
+		"bennt",
+		"benon",
+		"bente",
+		"bentz",
+		"benyu",
+		"benzi",
+		"beppe",
+		"beppo",
+		"berdj",
+		"berdy",
+		"beren",
+		"bergh",
+		"beril",
+		"berit",
+		"berje",
+		"berke",
+		"berky",
+		"berla",
+		"berly",
+		"berna",
+		"bernd",
+		"berni",
+		"berno",
+		"bernt",
+		"berny",
+		"berte",
+		"berti",
+		"berto",
+		"berty",
+		"besse",
+		"bessi",
+		"bessy",
+		"betha",
+		"bethe",
+		"betia",
+		"betka",
+		"betsi",
+		"betsy",
+		"bette",
+		"betti",
+		"betto",
+		"betul",
+		"beula",
+		"bevan",
+		"beven",
+		"bevin",
+		"bevis",
+		"bevon",
+		"bevys",
+		"beyla",
+		"beyle",
+		"bheki",
+		"biago",
+		"biala",
+		"biana",
+		"bibha",
+		"bijan",
+		"bikas",
+		"bilal",
+		"bilee",
+		"billa",
+		"bille",
+		"billi",
+		"bimia",
+		"bimla",
+		"binah",
+		"bindo",
+		"bindu",
+		"bingu",
+		"binky",
+		"binni",
+		"binod",
+		"binta",
+		"birdi",
+		"biren",
+		"birge",
+		"birky",
+		"birly",
+		"birny",
+		"birol",
+		"biron",
+		"birte",
+		"birty",
+		"biswa",
+		"bitta",
+		"bitya",
+		"bjorn",
+		"blaed",
+		"blasi",
+		"blayr",
+		"blaza",
+		"bleda",
+		"bledi",
+		"blima",
+		"blime",
+		"blixa",
+		"bluma",
+		"blyss",
+		"boase",
+		"bobbe",
+		"bobbi",
+		"bobek",
+		"bobie",
+		"bodan",
+		"bodee",
+		"bodey",
+		"bodie",
+		"bodil",
+		"bodin",
+		"bogna",
+		"boice",
+		"bojan",
+		"bonar",
+		"bondy",
+		"bonie",
+		"bonka",
+		"bonni",
+		"boona",
+		"boony",
+		"boote",
+		"bordy",
+		"boris",
+		"borja",
+		"borka",
+		"borya",
+		"borys",
+		"boski",
+		"botan",
+		"bowen",
+		"boxin",
+		"boyce",
+		"bozze",
+		"bradd",
+		"brade",
+		"brady",
+		"brahm",
+		"bramm",
+		"brana",
+		"brani",
+		"brann",
+		"braun",
+		"breah",
+		"brear",
+		"breay",
+		"bredo",
+		"breea",
+		"brena",
+		"brenn",
+		"breno",
+		"breon",
+		"breza",
+		"briah",
+		"brian",
+		"brice",
+		"bridd",
+		"bridi",
+		"bridy",
+		"brien",
+		"briet",
+		"brina",
+		"brinn",
+		"brion",
+		"brisa",
+		"brita",
+		"brodi",
+		"brona",
+		"broox",
+		"bruce",
+		"brucy",
+		"bruis",
+		"bruna",
+		"bruno",
+		"bruns",
+		"bryan",
+		"bryar",
+		"bryce",
+		"bryen",
+		"bryga",
+		"bryna",
+		"brynn",
+		"bryon",
+		"bubsy",
+		"buchi",
+		"bucky",
+		"budde",
+		"buell",
+		"buena",
+		"bugsy",
+		"bunky",
+		"bunni",
+		"burak",
+		"burch",
+		"burne",
+		"burrt",
+		"burtt",
+		"burty",
+		"busse",
+		"buzby",
+		"byram",
+		"byran",
+		"byrdy",
+		"byren",
+		"byrie",
+		"byrle",
+		"byrne",
+		"byrom",
+		"byron",
+		"cacey",
+		"cachi",
+		"cacia",
+		"cacie",
+		"cadao",
+		"cadby",
+		"cadel",
+		"caden",
+		"cadye",
+		"caeli",
+		"cahan",
+		"cahil",
+		"cahit",
+		"caily",
+		"caira",
+		"caisi",
+		"caity",
+		"caius",
+		"calab",
+		"calah",
+		"calan",
+		"calee",
+		"calem",
+		"caley",
+		"calia",
+		"calie",
+		"calli",
+		"cally",
+		"calum",
+		"calym",
+		"calyn",
+		"camar",
+		"camey",
+		"camia",
+		"cammi",
+		"canda",
+		"candi",
+		"cappi",
+		"capys",
+		"carah",
+		"caree",
+		"carel",
+		"caren",