--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/index.py Tue May 05 19:37:32 2009 +0300
@@ -0,0 +1,461 @@
+#!/usr/bin/python2.5
+import werkzeug
+from werkzeug.exceptions import HTTPException
+
+from PIL import Image, ImageDraw, ImageFont, ImageEnhance
+from cStringIO import StringIO
+import random, itertools, time, os.path
+
+if not hasattr(itertools, 'izip_longest') :
+
+ def izip_longest(*args, **kwds):
+ # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
+ fillvalue = kwds.get('fillvalue')
+ def sentinel(counter = ([fillvalue]*(len(args)-1)).pop):
+ yield counter() # yields the fillvalue, or raises IndexError
+ fillers = itertools.repeat(fillvalue)
+ iters = [itertools.chain(it, sentinel(), fillers) for it in args]
+ try:
+ for tup in itertools.izip(*iters):
+ yield tup
+ except IndexError:
+ pass
+
+ itertools.izip_longest = izip_longest
+
+class Defaults :
+ # settings
+
+ text_lang = 'en'
+
+ chars = [ u'"', u'!', u'?' ]
+
+ colors = [
+ "#0469af",
+ "#fbc614",
+ "#e1313b",
+ ]
+
+ font_name = 'helvetica'
+ font_size = 30
+
+ bg_color = "#ffffff"
+ line_spacing = -10
+ sharpness = 0.6
+
+ img_format = 'png'
+
+TEXT_BY_LANG = dict(
+ en = [
+ u"aalto",
+ u"unive",
+ u"rsity"
+ ],
+ fi = [
+ u"aalto",
+ u"yliop",
+ u"isto"
+ ],
+ se = [
+ u"aalto",
+ u"univer",
+ u"sitetet",
+ ],
+)
+
+STATIC_PATH = "static"
+
+FONTS = {
+ 'dejavu-sans-bold': "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf",
+ 'helvetica': "fonts/HELR65W.TTF",
+ }
+
+IMAGE_FORMATS = {
+ 'jpeg': 'jpeg',
+ 'jpg': 'jpeg',
+ 'png': 'png',
+ 'bmp': 'bmp'
+}
+
+FONT_SIZE_MAX = 1024
+IMG_SIZE_MAX = 1024
+
+TILE_SIZE = (100, 100)
+
+# enable debugging
+DEBUG = True
+
+
+def randomize (seq) :
+ """
+ Returns the given sequence in random order as a list
+ """
+
+ # copy
+ l = list(seq)
+
+ # rearrange
+ random.shuffle(l)
+
+ return l
+
+def randomize_str_char (str) :
+ """
+ Randomize the given string by moving one char around
+ """
+
+ l = list(str)
+
+ c = l.pop(random.randint(0, len(l) - 1))
+ l.insert(random.randint(0, len(l)), c)
+
+ return ''.join(l)
+
+def build_data (text, chars, line_colors, random_chars=True, random_text=False, random_text_char=False) :
+ """
+ Returns a matrix of (text, color) tuples representing the data to render
+
+ [ [ (str, str) ] ]
+
+ text - list of lines
+ chars - list of random chars to interpse
+ line_colors - list of colors to draw the chars in
+ random_chars - randomize the lines the chars go in
+ random_text - randomize the chars in each line
+ random_text_char - randomize each line by moving one char around
+ """
+
+ data = []
+
+ if random_chars :
+ chars = randomize(chars)
+
+ for line, char, color in itertools.izip_longest(text, chars, line_colors, fillvalue=None) :
+ # pick position to place char
+ pos = random.randint(1, len(line) - 1)
+
+ if not color :
+ color = "#000000"
+
+ if random_text :
+ line = ''.join(randomize(line))
+
+ if random_text_char :
+ line = randomize_str_char(line)
+
+ # split into three parts
+ if char :
+ data.append([
+ (line[:pos], "#000000"),
+ (char, color),
+ (line[pos:], "#000000"),
+ ])
+ else :
+ data.append([
+ (line, "#000000"),
+ ])
+
+ return data
+
+def load_font (font_name, font_size) :
+ """
+ Load a font by name
+ """
+
+ # load font
+ font_path = FONTS[font_name]
+ font = ImageFont.truetype(font_path, font_size)
+
+ return font
+
+def render_img (data, font, background_color="#ffffff", line_spacing=0, img_size=None) :
+ """
+ Render the data (as from build_data) as an image, using the given PIL.ImageFont, and return the PIL Image object
+ """
+
+ img_width = img_height = 0
+
+ img_data = []
+
+ # compute image/segment width/height
+ for segments in data :
+ line_width = line_height = 0
+
+ # build a new list of segments with additional info
+ line_segments = []
+
+ for seg_text, seg_color in segments :
+ # compute rendered text size
+ seg_width, seg_height = font.getsize(seg_text)
+
+ # update line_*
+ line_width += seg_width
+ line_height = max(line_height, seg_height)
+
+ # build the new segments list
+ line_segments.append((seg_text, seg_color, seg_width))
+
+ # update img_*
+ img_width = max(img_width, line_width)
+ img_height += line_height
+ img_data.append((line_segments, line_height))
+
+ if img_size :
+ # override size
+ img_width, img_height = img_size
+
+ else :
+ # calculate height needed for line spacing
+ img_height += (len(img_data) - 1) * line_spacing
+
+ # create image
+ img = Image.new("RGB", (img_width, img_height), background_color)
+ draw = ImageDraw.Draw(img)
+
+ # draw text
+ img_y = 0
+ for segments, line_height in img_data :
+ img_x = 0
+
+ # draw each segment build above, incremeing along img_x
+ for seg_text, seg_color, seg_width in segments :
+ draw.text((img_x, img_y), seg_text, font=font, fill=seg_color)
+
+ img_x += seg_width
+
+ img_y += line_height + line_spacing
+
+ return img
+
+def effect_sharpness (img, factor) :
+ """
+ Sharpen the image by the given factor
+ """
+
+ return ImageEnhance.Sharpness(img).enhance(factor)
+
+def build_img (img, format='png') :
+ """
+ Write the given PIL.Image as a string, returning the raw binary data
+
+ Format should be one of the PIL-supported image foarts
+ """
+
+ # render PNG output
+ buf = StringIO()
+ img.save(buf, format)
+ data = buf.getvalue()
+
+ return data
+
+def arg_bool (val) :
+ if val.lower() in ('true', 't', '1', 'yes', 'y') :
+ return True
+ elif val.lower() in ('false', 'f', '0', 'no', 'n') :
+ return False
+ else :
+ raise ValueError(val)
+
+def arg_color (val) :
+ if val.beginswith('#') :
+ int(val[1:], 16)
+
+ return val
+ else :
+ raise ValueError(val)
+
+class Option (object) :
+ def __init__ (self, name, is_list, type, default, range) :
+ self.name = name
+ self.is_list = is_list
+ self.type = type
+ self.default = default
+ self.range = range
+
+ def parse (self, args) :
+ if self.is_list :
+ if self.name in args :
+ return args.getlist(self.name, self.type)
+ else :
+ return self.default
+ else :
+ if self.type == arg_bool and not self.default and self.name in args :
+ return True
+
+ else :
+ return args.get(self.name, self.default, self.type)
+
+class Options (object) :
+ def __init__ (self, *options) :
+ self.options = options
+
+ def parse (self, args) :
+ return dict((opt.name, opt.parse(args)) for opt in self.options)
+
+OPTIONS = Options(
+ Option('lang', False, str, Defaults.text_lang, TEXT_BY_LANG.keys()),
+ Option('text', True, unicode, None, None),
+ Option('random-text', False, arg_bool, False, None),
+ Option('random-text-char', False, arg_bool, False, None),
+ Option('chars', True, unicode, Defaults.chars, None),
+ Option('random-chars', False, arg_bool, True, None),
+ Option('colors', True, arg_color, Defaults.colors, None),
+ Option('font', False, str, Defaults.font_name, FONTS.keys()),
+ Option('font-size', False, int, Defaults.font_size, None),
+ Option('bg-color', False, arg_color, Defaults.bg_color, None),
+ Option('line-spacing', False, int, Defaults.line_spacing, None),
+ Option('sharpness', False, float, Defaults.sharpness, None),
+ Option('image-format', False, str, Defaults.img_format, IMAGE_FORMATS.keys()),
+ Option('seed', False, int, None, None),
+ Option('img_width', False, int, None, None),
+ Option('img_height', False, int, None, None),
+)
+
+def handle_generic (req, img_size=None) :
+ # parse options
+ opts = OPTIONS.parse(req.args)
+
+ # postprocess
+ if opts['text'] is None :
+ opts['text'] = TEXT_BY_LANG[opts['lang']]
+
+ if opts['font-size'] > FONT_SIZE_MAX :
+ raise ValueError(opts['font-size'])
+
+ if opts['seed'] is None :
+ opts['seed'] = time.time()
+
+ if opts['img_width'] and opts['img_height'] :
+ img_size = (opts['img_width'], opts['img_height'])
+
+ if opts['img_width'] > IMG_SIZE_MAX or opts['img_height'] > IMG_SIZE_MAX :
+ raise ValueError(img_size)
+
+ # load/prep resources
+ random.seed(opts['seed'])
+ data = build_data(opts['text'], opts['chars'], opts['colors'], opts['random-chars'], opts['random-text'], opts['random-text-char'])
+ font = load_font(opts['font'], opts['font-size'])
+
+ # render the image
+ img = render_img(data, font, opts['bg-color'], opts['line-spacing'], img_size)
+
+ img = effect_sharpness(img, opts['sharpness'])
+
+ png_data = build_img(img, opts['image-format'])
+
+ # build the response
+ response = werkzeug.Response(png_data, mimetype='image/%s' % opts['image-format'])
+
+ return response
+
+def handle_help (req) :
+ return werkzeug.Response('\n'.join(
+ "%-15s %4s %-10s %-20s %s" % data for data in [
+ ("name", "", "type", "default", "range"),
+ ("", "", "", "", ""),
+ ] + [(
+ opt.name,
+ 'list' if opt.is_list else 'item',
+ opt.type.__name__,
+ repr(opt.default),
+ opt.range if opt.range else ""
+ ) for opt in OPTIONS.options]
+ ), mimetype='text/plain')
+
+def handle_logo (req) :
+ if 'help' in req.args :
+ return handle_help(req)
+
+ return handle_generic(req)
+
+def handle_tile (req) :
+ return handle_generic(req, img_size=TILE_SIZE)
+
+def handle_request (req) :
+ if req.path == '/' or req.path.startswith('/logo.') :
+ return handle_logo(req)
+
+ elif req.path == '/tile' :
+ return handle_tile(req)
+
+ else :
+ raise ValueError(req)
+
+@werkzeug.Request.application
+def wsgi_application (request) :
+ """
+ Our werkzeug WSGI handler
+ """
+
+ try :
+ # request -> response
+ response = handle_request(request)
+
+ return response
+
+ except HTTPException, e :
+ # return as HTTP response
+ return e
+
+def build_app () :
+ """
+ Build and return a WSGI application
+ """
+
+ app = wsgi_application
+
+ # add other middleware
+ app = werkzeug.SharedDataMiddleware(app, {
+ '/static': STATIC_PATH,
+ })
+
+
+ if DEBUG :
+ # enable debugging
+ app = werkzeug.DebuggedApplication(app, evalex=False)
+
+ return app
+
+def main_cgi (app) :
+ import wsgiref.handlers
+
+ handler = wsgiref.handlers.CGIHandler()
+ handler.run(app)
+
+def main_fastcgi (app) :
+ import flup.server.fcgi
+
+ server = flup.server.fcgi.WSGIServer(app,
+ multithreaded = False,
+ multiprocess = False,
+ multiplexed = False,
+
+ bindAddress = None,
+
+ umask = None,
+ debug = True,
+ )
+
+ server.run()
+
+def main () :
+ from sys import argv
+
+ # get our file extension
+ root, ext = os.path.splitext(argv[0])
+
+ # get our handler
+ handler = {
+ '.cgi': main_cgi,
+ '.fcgi': main_fastcgi,
+ }[ext]
+
+ # get our app
+ app = build_app()
+
+ # run
+ handler(app)
+
+if __name__ == '__main__' :
+ main()
+