#!/usr/bin/env python # CSS Map Builder v.01 # # Copyleft (2006) Andrew Gwozdziewycz # # This program is free software; you can redistribute and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA or visit # the Free Software Foundation website at http://www.fsf.org # # There's config options in the source. It's rough, but it works. # Also, there's quite a few bugs with the css parser, a lot of which stem # from the fact that its not really a real parser. # # When last checked, the comments removal was buggy, so it's commented out. # # ... import re, sys comment_re = '/\*.*?\*\/' block_re = '({.*?})' property_re = '{(;)}' color_re = re.compile('(#[0-9a-f-A-F]{3,6})') def remove_comments(text): # a = re.sub(comment_re, '', text) # print a return text def create_blocks(text): bits = filter(None, re.split(block_re, text)) # combine blocks into {'selector': 'content'} total = [] current = {} for bit in bits: if bit[0] == '{': # assume we've got a block current['content'] = bit # done with this block, i think... total.append(current) current = {} else: current['selector'] = bit return total def create_mapping(blocks): mapping = {} for block in blocks: selectors = [] if block.has_key('selector'): selectors = block['selector'].split(',') if block.has_key('content'): bits = filter(None, block['content'].split(';')) properties = [] for bit in bits: pbits = bit.split(':') if len(pbits) == 2: property = re.sub('[^a-zA-Z0-9-.]', '', pbits[0].split(':')[0]) value = re.sub('[;}]', '', pbits[1].strip()) properties.append((property, value)) for selector in selectors: selector = selector.strip() print '(%s)' % selector if mapping.has_key(selector): mapping[selector] = mapping[selector] + properties else: mapping[selector] = properties return mapping def parse(infile): try: fi = open(infile) except IOError: print >> sys.stderr, "Error opening file %s" % infile raise SystemExit content = fi.read() content = re.sub('\r|\n', '', content) content = remove_comments(content) print content blocks = create_blocks(content) mapping = create_mapping(blocks) return mapping import Image, ImageDraw config = {} config['left_padding'] = 50 config['right_padding'] = 50 config['top_padding'] = 100 config['bottom_padding'] = 100 config['text_padding'] = 0 config['center_width'] = 1600 config['cp_x_divisor'] = 3 config['cp_y_divisor'] = 5 config['text_height'] = 11 config['text_left_margin'] = 5 config['text_right_margin'] = 5 config['text_fill_color'] = '#666666' config['background_color'] = '#000000' config['use_curve_color'] = 1 config['use_curves'] = 1 config['alt_color_list'] = ['#000000','#000000'] config['alt_color_color'] = '#999999' config['no_color_color'] = '#666666' config['sort_selectors'] = 1 config['sort_properties'] = 1 def create_image(filename, mapping): im = Image.new("RGB", (1, 1)) imd = ImageDraw.Draw(im) selectors = mapping.keys() if config['sort_selectors']: selectors.sort() selector_widths = [] property_widths = [] text_height = 11 for sel in selectors: sz = imd.textsize(sel) selector_widths.append(sz[0]) selector_max_width = max(selector_widths) # properties setup tmp = {} for prop_list in mapping.values(): for p, v in prop_list: # add the property value for fun... tmp[p + ': ' + v + ';'] = 1 # tmp[p] = 1 # property alone properties = tmp.keys() # print '\n'.join(properties) if config['sort_properties']: properties.sort() for prp in properties: sz = imd.textsize(prp) property_widths.append(sz[0]) property_max_width = max(property_widths) tt = config['text_height'] + config['text_padding'] vpadding = config['top_padding'] + config['bottom_padding'] hpadding = config['left_padding'] + config['right_padding'] max_height = vpadding + max(len(properties), len(selectors)) * tt max_width = hpadding + 2*max(property_max_width, selector_max_width)\ + config['center_width'] print '%d Selectors' % len(selectors) print '%d Properties' % len(properties) print '%d property max width' % property_max_width print '%d selector max width' % selector_max_width print 'Canvas = %d, %d' % (max_width, max_height) im = Image.new("RGB", (max_width, max_height), config['background_color']) imd = ImageDraw.Draw(im) text_max_width = max(selector_max_width, property_max_width) # selectors for ind, t in enumerate(zip(selectors, selector_widths)): sel = t[0] wid = t[1] x = config['left_padding'] + text_max_width - wid y = config['top_padding'] + ind * config['text_height'] imd.text((x, y), sel, fill=config['text_fill_color']) # properties x = config['center_width'] + config['left_padding'] + text_max_width for ind, t in enumerate(zip(properties, property_widths)): prp = t[0] y = config['top_padding'] + ind * config['text_height'] imd.text((x, y), prp, config['text_fill_color']) # lines to properties startx = config['left_padding'] + text_max_width + config['text_right_margin'] endx = x - config['text_left_margin'] tto2 = config['text_height'] / 2.0 for key, props in mapping.iteritems(): ind = selectors.index(key) starty = config['top_padding'] + (ind * config['text_height']) + tto2 for prp, dum in props: prp = prp + ': ' + dum + ';' if config['use_curve_color']: m = color_re.search(dum) if m: color = m.group(1) else: color = None ind = properties.index(prp) # if mode were centered here... this would be more difficult endy = config['top_padding'] + (ind * config['text_height']) + tto2 if config['use_curves']: cp = [(startx, starty), None, None, (endx, endy)] eysy = abs(endy-starty) / config['cp_x_divisor'] yd = config['cp_y_divisor'] if endy > starty: # cp[1] = (startx, starty - 60) # cp[2] = (endx, endy + 60) cp[1] = (startx + eysy, starty - (endy - starty) / yd) cp[2] = (endx - eysy, endy + (endy - starty) / yd) else: cp[1] = (startx + eysy, starty + (starty - endy) / yd) cp[2] = (endx - eysy, endy - (starty - endy) / yd) if config['use_curve_color']: if color and color.lower in config['alt_color_list']: color = config['alt_color_color'] else: color = config['no_color_color'] if not color: color = config['no_color_color'] if config['use_curves']: draw_bezier(imd, cp, fill=color) else: imd.line([startx, starty, endx, endy], fill=color) # imd.line([startx, starty, endx, endy]) im.save(filename, "JPEG") ### Beizer curve from wikipedia def compute_point_on_bezier(cp, t): cx = 3.0 * (cp[1][0] - cp[0][0]) bx = 3.0 * (cp[2][0] - cp[1][0]) - cx ax = cp[3][0] - cp[0][0] - cx - bx cy = 3.0 * (cp[1][1] - cp[0][1]) by = 3.0 * (cp[2][1] - cp[1][1]) - cy ay = cp[3][1] - cp[0][1] - cy - by # /* calculate the curve point at parameter value t */ tSquared = t * t tCubed = tSquared * t # result (x, y) return ( (ax * tCubed) + (bx * tSquared) + (cx * t) + cp[0][0], (ay * tCubed) + (by * tSquared) + (cy * t) + cp[0][1] ) def compute_bezier(cp, num_points=1000): ''' cp is a 4 element array where: cp[0] is the starting point, or A in the above diagram cp[1] is the first control point, or B cp[2] is the second control point, or C cp[3] is the end point, or D ''' curve = [] dt = 1.0 / (num_points - 1) for i in range(0, num_points): curve.append(compute_point_on_bezier(cp, i*dt)) return curve def draw_bezier(imd, cp, *args, **options): num_points = 1000 dt = 1.0 / (num_points - 1) p1 = compute_point_on_bezier(cp, 0) for i in range(0, num_points): p2 = compute_point_on_bezier(cp, i*dt) imd.line(p1 + p2, **options) p1 = p2 if __name__ == "__main__": if len(sys.argv) > 2: parsed = parse(sys.argv[1]) else: print >>sys.stderr, "usage: %s cssfile imagefile.jpg\n" % sys.argv[0] raise SystemExit keys = parsed.keys() keys.sort() for key in keys: properties = [] for p, v in parsed[key]: properties.append(p) print key, '\n\t', ' '.join(properties) create_image(sys.argv[2], parsed) # for selector, properties in parsed.iteritems(): # print selector, '{' # for p, v in properties: # print '\t%s: %s;' % (p, v) # print '}'