aafigure-0.5/0000755000175000017500000000000011352464606011246 5ustar lchlchaafigure-0.5/CHANGES.txt0000644000175000017500000000255211352454003013051 0ustar lchlch============================= Change history for aafigure ============================= aafigure 0.5 2010-03-24 Bugs fixed (launchpad.net IDs): - #392949 aafigure fails to run examples : missing font files cause breakage - #411253 KeyError is raised for PDF rendering with -O font= option - #411255 Importing PIL fails on some installation - #411257 East-asian wideglyphs breaks alignment - #462593 KeyError: 'format' when using through MoinMoin parser "aafig" - #529409 aafigured SVGs are not rendered by evince, nautilus-thumbnailer, zim and perhaps others aafigure 0.4 2009-07-04 Internal code cleanups, add "process" function to external API. Improved setup.py and MANIFEST.in for a more complete source distribution. Documentation is done with Sphinx. Command line tool: added --option. Several bug fixes. aafigure 0.3 2009-06-13 Added type detection by filename. Added examples and man page and did some internal cleanups. aafigure 0.2 2009-06-12 Separated into standalone project. Previously it was found in the docutils sandbox. The Docutils plugin for the aafigure directive is separated and aafigure is now a standalone package and command line tool. The plugin is still available as separate package that uses aafigure as dependency. Added Debian control files to build a package. aafigure-0.5/README.txt0000644000175000017500000000173111223514323012733 0ustar lchlch================= aafigure README ================= aafigure is an ASCII art to image converter. DD o---> ASCII art figures can be parsed and output as SVG, PNG, JPEG, PDF and more. This project provides a Python package, a command line script as well as Docutils and MoinMoin plugins. An Ubuntu package is available: https://launchpad.net/~aafigure-team/+archive/ppa The project is also registered in PyPi: http://pypi.python.org/pypi/aafigure The project is managed in Launchpad (Answers, Bug Tracking and Code) https://launchpad.net/aafigure License: Simplified BSD License, see LICENSE.txt Documentation ============= A manual can be found in the documentation directory. The .rst files can be looked at with a text editor or they can be compiled to HTML or PDF using Sphinx. Installation ============ Detailed instructions about different install methods are described in the file documentation/manual.rst. Short version: python setup.py install aafigure-0.5/aafigure/0000755000175000017500000000000011352464606013031 5ustar lchlchaafigure-0.5/aafigure/pdf.py0000644000175000017500000001403411316525703014152 0ustar lchlch"""\ PDF renderer for the aafigure package. (C) 2008 Chris Liechti This is open source software under the BSD license. See LICENSE.txt for more details. """ import sys from error import UnsupportedFormatError try: import reportlab from reportlab.lib import colors from reportlab.graphics.shapes import * from reportlab.graphics import renderPDF from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont except ImportError: raise UnsupportedFormatError('please install Reportlab to get PDF output support') class PDFOutputVisitor: """Render a list of shapes as PDF vector image.""" def __init__(self, options): """\ Dual use as PDF file writer or as Reportlab Drawing generator. files: file_like or filname is given in the options: The PDF file is written there. Drawing: file_like and filname are missing in the options: No output is generated. The Drawing can be used for example:: visitor = PDFOutputVisitor(None, ...) do_something(renderPDF.GraphicsFlowable(visitor.drawing)) """ self.options = options self.scale = 4*options['scale'] self.line_width = 0.4*options['line_width'] self.foreground = options['foreground'] self.background = options['background'] self.fillcolor = options['fill'] # if front is given explicit, use it instead of textual/proportional flags if 'font' in options: self.font = options['font'] if self.font.endswith('.ttf'): # ttf support pdfmetrics.registerFont(TTFont(self.font, self.font)) else: if options['proportional']: self.font = 'Helvetica' else: self.font = 'Courier' def _num(self, number): """helper to format numbers with scale for PDF output""" return number*self.scale def _color(self, color): return colors.HexColor(color) def visit_image(self, aa_image): """Process the given ASCIIArtFigure and output the shapes in the PDF file """ self.aa_image = aa_image # save for later XXX not optimal to do it here self.width = (aa_image.width)*aa_image.nominal_size*aa_image.aspect_ratio self.height = (aa_image.height)*aa_image.nominal_size self.drawing = Drawing(self._num(self.width), self._num(self.height)) self.visit_shapes(aa_image.shapes) # if file is given, write if 'file_like' in self.options: renderPDF.drawToFile(self.drawing, self.options['file_like'], '') def visit_shapes(self, shapes): for shape in shapes: shape_name = shape.__class__.__name__.lower() visitor_name = 'visit_%s' % shape_name if hasattr(self, visitor_name): getattr(self, visitor_name)(shape) else: sys.stderr.write("WARNING: don't know how to handle shape %r\n" % shape) # - - - - - - PDF drawing helpers - - - - - - - def _line(self, x1, y1, x2, y2, thick): """Draw a line, coordinates given as four decimal numbers""" self.drawing.add(Line( self._num(x1), self._num(self.height - y1), self._num(x2), self._num(self.height - y2), strokeColor = self._color(self.foreground), strokeWidth = self.line_width*(1 + 0.5*bool(thick)) )) def _rectangle(self, x1, y1, x2, y2, style=''): """Draw a rectangle, coordinates given as four decimal numbers.""" if x1 > x2: x1, x2 = x2, x1 if y1 > y2: y1, y2 = y2, y1 self.drawing.add(Rect( self._num(x1), self._num(self.height - y2), self._num(x2-x1), self._num(y2 - y1), fillColor = self._color(self.fillcolor), strokeWidth = self.line_width )) # - - - - - - visitor function for the different shape types - - - - - - - def visit_point(self, point): self.drawing.add(Circle( self._num(point.x), self._num(self.height - point.y), self._num(0.2), fillColor = self._color(self.foreground), strokeWidth = self.line_width )) def visit_line(self, line): x1, x2 = line.start.x, line.end.x y1, y2 = line.start.y, line.end.y self._line(x1, y1, x2, y2, line.thick) def visit_rectangle(self, rectangle): self._rectangle( rectangle.p1.x, rectangle.p1.y, rectangle.p2.x, rectangle.p2.y ) def visit_circle(self, circle): self.drawing.add(Circle( self._num(circle.center.x), self._num(self.height - circle.center.y), self._num(circle.radius), strokeColor = self._color(self.foreground), fillColor = self._color(self.fillcolor), strokeWidth = self.line_width )) def visit_label(self, label): # font-weight="bold" style="stroke:%s" self.drawing.add(String( self._num(label.position.x), self._num(self.height - label.position.y + self.aa_image.nominal_size*0.2), label.text, fontSize = self._num(self.aa_image.nominal_size), fontName = self.font, fillColor = self._color(self.foreground), )) def visit_group(self, group): # XXX could add a group to the PDF file self.visit_shapes(group.shapes) def visit_arc(self, arc): p1, p2 = arc.start, arc.end c1 = arc.start_control_point() c2 = arc.end_control_point() path = Path(strokeColor = self._color(self.foreground), strokeWidth = self.line_width) path.moveTo (self._num(p1.x), self._num(self.height - p1.y)) path.curveTo(self._num(c1.x), self._num(self.height - c1.y), self._num(c2.x), self._num(self.height - c2.y), self._num(p2.x), self._num(self.height - p2.y)) self.drawing.add(path) aafigure-0.5/aafigure/pil.py0000644000175000017500000001376511352451604014175 0ustar lchlch"""\ Bitmap renderer for the aafigure package, using the Python Imaging Library. (C) 2006 Chris Liechti This is open source software under the BSD license. See LICENSE.txt for more details. """ import sys from error import UnsupportedFormatError PIL_OK = False try: import Image, ImageDraw PIL_OK = True except ImportError: pass if PIL_OK is False: try: from PIL import Image, ImageDraw except ImportError: raise UnsupportedFormatError('please install PIL to get bitmaps output support') import PILhelper class PILOutputVisitor: """Render a list of shapes as bitmap. """ def __init__(self, options): self.options = options self.scale = options['scale']*8 self.debug = options['debug'] self.line_width = options['line_width'] self.foreground = options['foreground'] self.background = options['background'] self.fillcolor = options['fill'] def _num(self, number): return number * self.scale def visit_image(self, aa_image): """Process the given ASCIIArtFigure and draw the shapes in the bitmap file """ self.aa_image = aa_image # save for later XXX not optimal to do it here self.width = (aa_image.width+1)*aa_image.nominal_size*aa_image.aspect_ratio self.height = (aa_image.height+1)*aa_image.nominal_size # if font is given explicit, use it instead of proportional flag font_size = int(self._num(self.aa_image.nominal_size*1.1)) if 'font' in self.options: self.font = PILhelper.font_by_name(self.options['font'], font_size) else: self.font = PILhelper.font_by_type(self.options['proportional'], font_size) if self.font is None: sys.stderr.write("WARNING: font not found, using PIL default font\n") self.image = Image.new( 'RGB', (int(self._num(self.width)), int(self._num(self.height))), self.background ) self.draw = ImageDraw.Draw(self.image) #~ if self.debug: #~ #draw a rectangle around entire image #~ self._rectangle( #~ 0,0, #~ aa_image.width, aa_image.height, #~ style = 'fill:none;', #~ ) self.visit_shapes(aa_image.shapes) del self.draw file_type = self.options['format'].lower() if file_type == 'jpg': file_type = 'jpeg' # alias try: if 'file_like' in self.options: self.image.save(self.options['file_like'], file_type) except KeyError: raise UnsupportedFormatError("PIL doesn't support image format %r" % file_type) def visit_shapes(self, shapes): for shape in shapes: shape_name = shape.__class__.__name__.lower() visitor_name = 'visit_%s' % shape_name if hasattr(self, visitor_name): getattr(self, visitor_name)(shape) else: sys.stderr.write("WARNING: don't know how to handle shape %r\n" % shape) def visit_group(self, group): self.visit_shapes(group.shapes) # - - - - - - drawing helpers - - - - - - - def _line(self, x1, y1, x2, y2): """Draw a line, coordinates given as four decimal numbers""" self.draw.line((self._num(x1), self._num(y1), self._num(x2), self._num(y2)), fill=self.foreground) #self.line_width def _rectangle(self, x1, y1, x2, y2): """Draw a rectangle, coordinates given as four decimal numbers. ``style`` is inserted in the SVG. It could be e.g. "fill:yellow" """ self.draw.rectangle((self._num(x1), self._num(y1), self._num(x2), self._num(y2)), fill=self.fillcolor, outline=self.foreground) #self.line_width # - - - - - - visitor function for the different shape types - - - - - - - def visit_point(self, point): dotsize = 2 self.draw.ellipse( ( self._num(point.x)-dotsize, self._num(point.y)-dotsize, self._num(point.x)+dotsize, self._num(point.y)+dotsize ), fill=self.foreground ) def visit_line(self, line): x1, x2 = line.start.x, line.end.x y1, y2 = line.start.y, line.end.y self._line(x1, y1, x2, y2) def visit_rectangle(self, rectangle): self._rectangle( rectangle.p1.x, rectangle.p1.y, rectangle.p2.x, rectangle.p2.y, ) def visit_circle(self, circle): self.draw.ellipse( ( self._num(circle.center.x-circle.radius), self._num(circle.center.y-circle.radius), self._num(circle.center.x+circle.radius), self._num(circle.center.y+circle.radius) ), fill=self.fillcolor, outline=self.foreground, ) def visit_label(self, label): # font-weight="bold" self.draw.text( (self._num(label.position.x), self._num(label.position.y-self.aa_image.nominal_size*1.1)), label.text, fill=self.foreground, font=self.font ) def _bezier(self, p1, c1, c2, p2, level=1): # de Casteljau's algorithm if self._num(p1.distance(p2)) <= 3: self._line(p1.x, p1.y, p2.x, p2.y) else: cmid = c1.midpoint(c2) lp1 = p1 lc1 = p1.midpoint(c1) lc2 = lc1.midpoint(cmid) rp2 = p2 rc2 = p2.midpoint(c2) rc1 = rc2.midpoint(cmid) lp2 = rc1.midpoint(lc2) rp1 = lp2 self._bezier(lp1, lc1, lc2, lp2, level + 1) self._bezier(rp1, rc1, rc2, rp2, level + 1) def visit_arc(self, arc): p1, p2 = arc.start, arc.end c1 = arc.start_control_point() c2 = arc.end_control_point() self._bezier(p1, c1, c2, p2) aafigure-0.5/aafigure/error.py0000644000175000017500000000043611217026056014530 0ustar lchlch"""\ Exception classes for the aafigure package. (C) 2009 Chris Liechti This is open source software under the BSD license. See LICENSE.txt for more details. """ class UnsupportedFormatError(Exception): """Error message when an unknown format is specified""" aafigure-0.5/aafigure/aafigure.py0000644000175000017500000013170511352454150015166 0ustar lchlch#!/usr/bin/env python """\ ASCII art to image converter. This is the main module that contains the parser. See svg.py and aa.py for output modules, that can render the parsed structure. (C) 2006-2009 Chris Liechti This is open source software under the BSD license. See LICENSE.txt for more details. """ import codecs from error import UnsupportedFormatError from shapes import * from unicodedata import east_asian_width import sys NOMINAL_SIZE = 2 CLASS_LINE = 'line' CLASS_STRING = 'str' CLASS_RECTANGLE = 'rect' CLASS_JOIN = 'join' CLASS_FIXED = 'fixed' DEFAULT_OPTIONS = dict( background = '#ffffff', foreground = '#000000', line_width = 2.0, scale = 1.0, aspect = 1.0, format = 'svg', debug = False, textual = False, proportional = False, encoding = 'utf-8', widechars = 'F,W', ) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class AsciiArtImage: """This class hold a ASCII art figure and has methods to parse it. The resulting list of shapes is also stored here. The image is parsed in 2 steps: 1. horizontal string detection. 2. generic shape detection. Each character that is used in a shape or string is tagged. So that further searches don't include it again (e.g. text in a string touching a fill), respectively can use it correctly (e.g. join characters when two or more lines hit). """ QUOTATION_CHARACTERS = list('"\'`') def __init__(self, text, aspect_ratio=1, textual=False, widechars='F,W'): """Take a ASCII art figure and store it, prepare for ``recognize``""" self.aspect_ratio = float(aspect_ratio) self.textual = textual # XXX TODO tab expansion # detect size of input image, store as list of lines self.image = [] max_x = 0 y = 0 # define character widths map charwidths = {} for key in ['F', 'H', 'W', 'Na', 'A', 'N']: if key in widechars.split(','): charwidths[key] = 2 else: charwidths[key] = 1 for line in text.splitlines(): # extend length by 1 for each wide glyph line_len = sum(charwidths[east_asian_width(c)] for c in line) max_x = max(max_x, line_len) # pad a space for each wide glyph padded_line = ''.join(c+' '*(charwidths[east_asian_width(c)]-1) for c in line) self.image.append(padded_line) y += 1 self.width = max_x self.height = y # make sure it's rectangular (extend short lines to max width) for y, line in enumerate(self.image): if len(line) < max_x: self.image[y] = line + ' '*(max_x-len(line)) # initialize other data structures self.classification = [[None]*self.width for y in range(self.height)] self.shapes = [] self.nominal_size = NOMINAL_SIZE def __str__(self): """Return the original image""" return '\n'.join([self.image[y] for y in range(self.height)]) def get(self, x, y): """Get character from image. Gives no error for access out of bounds, just returns a space. This simplifies the scanner functions. """ if 0 <= x < self.width and 0 <= y < self.height: return self.image[y][x] else: return ' ' def tag(self, coordinates, classification): """Tag coordinates as used, store classification""" for x, y in coordinates: self.classification[y][x] = classification def cls(self, x, y): """get tag at coordinate""" try: return self.classification[y][x] except IndexError: return 'outside' # Coordinate conversion and shifting def left(self, x): return x*NOMINAL_SIZE*self.aspect_ratio def hcenter(self, x): return (x + 0.5)*NOMINAL_SIZE*self.aspect_ratio def right(self, x): return (x + 1)*NOMINAL_SIZE*self.aspect_ratio def top(self, y): return y*NOMINAL_SIZE def vcenter(self, y): return (y + 0.5)*NOMINAL_SIZE def bottom(self, y): return (y + 1)*NOMINAL_SIZE def recognize(self): """ Try to convert ASCII art to vector graphics. The result is stored in ``self.shapes``. """ # XXX search for symbols #~ #search for long strings #~ for y in range(self.height): #~ for x in range(self.width): #~ character = self.image[y][x] #~ if self.classification[y][x] is None: #~ if character.isalnum(): #~ self.shapes.extend( #~ self._follow_horizontal_string(x, y) #~ ) # search for quoted texts for y in range(self.height): for x in range(self.width): #if not yet classified, check for a line character = self.image[y][x] if character in self.QUOTATION_CHARACTERS and self.classification[y][x] is None: self.shapes.extend( self._follow_horizontal_string(x, y, quoted=True)) # search for standard shapes for y in range(self.height): for x in range(self.width): #if not yet classified, check for a line character = self.image[y][x] if self.classification[y][x] is None: if character == '-': self.shapes.extend(self._follow_horizontal_line(x, y)) elif character == '|': self.shapes.extend(self._follow_vertical_line(x, y)) elif character == '_': self.shapes.extend(self._follow_lower_horizontal_line(x, y)) elif character == '~': self.shapes.extend(self._follow_upper_horizontal_line(x, y)) elif character == '=': self.shapes.extend(self._follow_horizontal_line(x, y, thick=True)) elif character in '\\/': self.shapes.extend(self._follow_rounded_edge(x, y)) elif character == '+': self.shapes.extend(self._plus_joiner(x, y)) elif character in self.FIXED_CHARACTERS: self.shapes.extend(self.get_fixed_character(character)(x, y)) self.tag([(x, y)], CLASS_FIXED) elif character in self.FILL_CHARACTERS: if self.textual: if self.get(x, y+1) == character: self.shapes.extend(self._follow_fill(character, x, y)) else: if (self.get(x+1, y) == character or self.get(x, y+1) == character): self.shapes.extend(self._follow_fill(character, x, y)) # search for short strings too for y in range(self.height): for x in range(self.width): character = self.image[y][x] if self.classification[y][x] is None: if character != ' ': self.shapes.extend(self._follow_horizontal_string(x, y, accept_anything=True)) # - - - - - - - - - helper function for some shapes - - - - - - - - - # Arrow drawing functions return the (new) starting point of the line and a # list of shapes that draw the arrow. The line itself is not included in # the list of shapes. The stating point is p1, possibly modified to match # the shape of the arrow head. # # Use complex numbers as 2D vectors as that means easy transformations like # scaling, rotation and translation # - - - - - - - - - arrows - - - - - - - - - def _standard_arrow(self, p1, p2): """--> return a possibly modified starting point and a list of shapes """ direction_vector = p1 - p2 direction_vector /= abs(direction_vector) return p1, [ Line(p1, p1-direction_vector*1.5+direction_vector*0.5j), Line(p1, p1-direction_vector*1.5+direction_vector*-0.5j) ] def _reversed_arrow(self, p1, p2): """--<""" direction_vector = p1 - p2 direction_vector /= abs(direction_vector) return p1-direction_vector*2, [ Line(p1-direction_vector*2.0, p1+direction_vector*(-0.5+0.5j)), Line(p1-direction_vector*2.0, p1+direction_vector*(-0.5-0.5j)) ] def _circle_head(self, p1, p2, radius=0.5): """--o""" direction_vector = p1 - p2 direction_vector /= abs(direction_vector) return p1-direction_vector, [Circle(p1-direction_vector, radius)] def _large_circle_head(self, p1, p2): """--O""" return self._circle_head(p1, p2, radius=0.9) def _rectangular_head(self, p1, p2): """--#""" direction_vector = p1 - p2 direction_vector /= abs(direction_vector) #~ return p1-direction_vector*1.414, [ #~ Rectangle(p1-direction_vector-direction_vector*(0.707+0.707j), #~ p1-direction_vector+direction_vector*(0.707+0.707j)) #~ ] return p1-direction_vector*1.707, [ Line(p1-direction_vector-direction_vector*(0.707+0.707j), p1-direction_vector-direction_vector*(0.707-0.707j)), Line(p1-direction_vector+direction_vector*(0.707+0.707j), p1-direction_vector+direction_vector*(0.707-0.707j)), Line(p1-direction_vector-direction_vector*(0.707+0.707j), p1-direction_vector+direction_vector*(0.707-0.707j)), Line(p1-direction_vector-direction_vector*(0.707-0.707j), p1-direction_vector+direction_vector*(0.707+0.707j)), ] # the same character can mean a different thing, depending from where the # line is coming. this table maps line direction (dx,dy) and the arrow # character to a arrow drawing function ARROW_TYPES = [ #chr dx dy arrow type ('>', 1, 0, '_standard_arrow'), ('<', -1, 0, '_standard_arrow'), ('^', 0, -1, '_standard_arrow'), ('A', 0, -1, '_standard_arrow'), ('V', 0, 1, '_standard_arrow'), ('v', 0, 1, '_standard_arrow'), ('>', -1, 0, '_reversed_arrow'), ('<', 1, 0, '_reversed_arrow'), ('^', 0, 1, '_reversed_arrow'), ('V', 0, -1, '_reversed_arrow'), ('v', 0, -1, '_reversed_arrow'), ('o', 1, 0, '_circle_head'), ('o', -1, 0, '_circle_head'), ('o', 0, -1, '_circle_head'), ('o', 0, 1, '_circle_head'), ('O', 1, 0, '_large_circle_head'), ('O', -1, 0, '_large_circle_head'), ('O', 0, -1, '_large_circle_head'), ('O', 0, 1, '_large_circle_head'), ('#', 1, 0, '_rectangular_head'), ('#', -1, 0, '_rectangular_head'), ('#', 0, -1, '_rectangular_head'), ('#', 0, 1, '_rectangular_head'), ] ARROW_HEADS = list('<>AVv^oO#') def get_arrow(self, character, dx, dy): """return arrow drawing function or None""" for head, ddx, ddy, function_name in self.ARROW_TYPES: if character == head and dx == ddx and dy == ddy: return getattr(self, function_name) # - - - - - - - - - fills - - - - - - - - - # Fill functions return a list of shapes. Each one if covering one cell # size. def _hatch_left(self, x, y): return self._n_hatch_diagonal(x, y, 1, True) def _hatch_right(self, x, y): return self._n_hatch_diagonal(x, y, 1, False) def _cross_hatch(self, x, y): return self._n_hatch_diagonal(x, y, 1, True) + \ self._n_hatch_diagonal(x, y, 1, False) def _double_hatch_left(self, x, y): return self._n_hatch_diagonal(x, y, 2, True) def _double_hatch_right(self, x, y): return self._n_hatch_diagonal(x, y, 2, False) def _double_cross_hatch(self, x, y): return self._n_hatch_diagonal(x, y, 2, True) + \ self._n_hatch_diagonal(x, y, 2, False) def _triple_hatch_left(self, x, y): return self._n_hatch_diagonal(x, y, 3, True) def _triple_hatch_right(self, x, y): return self._n_hatch_diagonal(x, y, 3, False) def _triple_cross_hatch(self, x, y): return self._n_hatch_diagonal(x, y, 3, True) + \ self._n_hatch_diagonal(x, y, 3, False) def _n_hatch_diagonal(self, x, y, n, left=False): """hatch generator function""" d = 1/float(n) result = [] if left: for i in range(n): result.append(Line( Point(self.left(x), self.top(y+d*i)), Point(self.right(x-d*i), self.bottom(y)) )) if n: result.append(Line( Point(self.right(x-d*i), self.top(y)), Point(self.right(x), self.top(y+d*i)) )) else: for i in range(n): result.append(Line(Point(self.left(x), self.top(y+d*i)), Point(self.left(x+d*i), self.top(y)))) if n: result.append(Line(Point(self.left(x+d*i), self.bottom(y)), Point(self.right(x), self.top(y+d*i)))) return result def _hatch_v(self, x, y): return self._n_hatch_straight(x, y, 1, True) def _hatch_h(self, x, y): return self._n_hatch_straight(x, y, 1, False) def _hv_hatch(self, x, y): return self._n_hatch_straight(x, y, 1, True) + \ self._n_hatch_straight(x, y, 1, False) def _double_hatch_v(self, x, y): return self._n_hatch_straight(x, y, 2, True) def _double_hatch_h(self, x, y): return self._n_hatch_straight(x, y, 2, False) def _double_hv_hatch(self, x, y): return self._n_hatch_straight(x, y, 2, True) + \ self._n_hatch_straight(x, y, 2, False) def _triple_hatch_v(self, x, y): return self._n_hatch_straight(x, y, 3, True) def _triple_hatch_h(self, x, y): return self._n_hatch_straight(x, y, 3, False) def _triple_hv_hatch(self, x, y): return self._n_hatch_straight(x, y, 3, True) + \ self._n_hatch_straight(x, y, 3, False) def _n_hatch_straight(self, x, y, n, vertical=False): """hatch generator function""" d = 1/float(n) offset = 1.0/(n+1) result = [] if vertical: for i in range(n): i = i + offset result.append(Line( Point(self.left(x+d*i), self.top(y)), Point(self.left(x+d*i), self.bottom(y)) )) #~ if n: #~ result.append(Line(Point(self.right(x-d*i), self.top(y)), Point(self.right(x), self.top(y+d*i)))) else: for i in range(n): i = i + offset result.append(Line( Point(self.left(x), self.top(y+d*i)), Point(self.right(x), self.top(y+d*i)) )) #~ if n: #~ result.append(Line(Point(self.left(x+d*i), self.bottom(y)), Point(self.right(x), self.top(y+d*i)))) return result def _fill_trail(self, x, y): return [ Line( Point(self.left(x+0.707), self.top(y)), Point(self.right(x), self.bottom(y-0.707)) ), Line( Point(self.left(x), self.top(y+0.707)), Point(self.right(x-0.707), self.bottom(y)) ) ] def _fill_foreground(self, x, y): return [ Rectangle( Point(self.left(x), self.top(y)), Point(self.right(x), self.bottom(y)) ) ] def _fill_background(self, x, y): return [] def _fill_small_circle(self, x, y): return [ Circle(Point(self.left(x+0.5), self.top(y+0.5)), 0.2) ] def _fill_medium_circle(self, x, y): return [ Circle(Point(self.left(x+0.5), self.top(y+0.5)), 0.4) ] def _fill_large_circle(self, x, y): return [ Circle(Point(self.left(x+0.5), self.top(y+0.5)), 0.9) ] def _fill_qmark(self, x, y): return [ Label(Point(self.left(x), self.bottom(y)), '?') ] def _fill_triangles(self, x, y): return [ Line(Point(self.left(x+0.5), self.top(y+0.2)), Point(self.left(x+0.75), self.top(y+0.807))), Line(Point(self.left(x+0.7), self.top(y+0.807)), Point(self.left(x+0.25), self.top(y+0.807))), Line(Point(self.left(x+0.25), self.top(y+0.807)), Point(self.left(x+0.5), self.top(y+0.2))), ] FILL_TYPES = [ ('A', '_hatch_left'), ('B', '_hatch_right'), ('C', '_cross_hatch'), ('D', '_double_hatch_left'), ('E', '_double_hatch_right'), ('F', '_double_cross_hatch'), ('G', '_triple_hatch_left'), ('H', '_triple_hatch_right'), ('I', '_triple_cross_hatch'), ('J', '_hatch_v'), ('K', '_hatch_h'), ('L', '_hv_hatch'), ('M', '_double_hatch_v'), ('N', '_double_hatch_h'), ('O', '_double_hv_hatch'), ('P', '_triple_hatch_v'), ('Q', '_triple_hatch_h'), ('R', '_triple_hv_hatch'), ('S', '_fill_qmark'), ('T', '_fill_trail'), ('U', '_fill_small_circle'), ('V', '_fill_medium_circle'), ('W', '_fill_large_circle'), ('X', '_fill_foreground'), ('Y', '_fill_triangles'), ('Z', '_fill_background'), ] FILL_CHARACTERS = ''.join([t+t.lower() for (t, f) in FILL_TYPES]) def get_fill(self, character): """return fill function""" for head, function_name in self.FILL_TYPES: if character == head: return getattr(self, function_name) raise ValueError('no such fill type') # - - - - - - - - - fixed characters and their shapes - - - - - - - - - def _open_triangle_left(self, x, y): return [ Line( Point(self.left(x), self.vcenter(y)), Point(self.right(x), self.top(y)) ), Line( Point(self.left(x), self.vcenter(y)), Point(self.right(x), self.bottom(y)) ) ] def _open_triangle_right(self, x, y): return [ Line( Point(self.right(x), self.vcenter(y)), Point(self.left(x), self.top(y)) ), Line( Point(self.right(x), self.vcenter(y)), Point(self.left(x), self.bottom(y)) ) ] def _circle(self, x, y): return [ Circle(Point(self.hcenter(x), self.vcenter(y)), NOMINAL_SIZE/2.0) ] FIXED_TYPES = [ ('{', '_open_triangle_left'), ('}', '_open_triangle_right'), ('*', '_circle'), ] FIXED_CHARACTERS = ''.join([t for (t, f) in FIXED_TYPES]) def get_fixed_character(self, character): """return fill function""" for head, function_name in self.FIXED_TYPES: if character == head: return getattr(self, function_name) raise ValueError('no such character') # - - - - - - - - - helper function for shape recognition - - - - - - - - - def _follow_vertical_line(self, x, y): """find a vertical line with optional arrow heads""" # follow line to the bottom _, end_y, line_end_style = self._follow_line(x, y, dy=1, line_character='|') # follow line to the top _, start_y, line_start_style = self._follow_line(x, y, dy=-1, line_character='|') # if a '+' follows a line, then the line is stretched to hit the '+' center start_y_fix = end_y_fix = 0 if self.get(x, start_y - 1) == '+': start_y_fix = -0.5 if self.get(x, end_y + 1) == '+': end_y_fix = 0.5 # tag characters as used (not the arrow heads) self.tag([(x, y) for y in range(start_y, end_y + 1)], CLASS_LINE) # return the new shape object with arrows etc. p1 = complex(self.hcenter(x), self.top(start_y + start_y_fix)) p2 = complex(self.hcenter(x), self.bottom(end_y + end_y_fix)) shapes = [] if line_start_style: p1, arrow_shapes = line_start_style(p1, p2) shapes.extend(arrow_shapes) if line_end_style: p2, arrow_shapes = line_end_style(p2, p1) shapes.extend(arrow_shapes) shapes.append(Line(p1, p2)) return group(shapes) def _follow_horizontal_line(self, x, y, thick=False): """find a horizontal line with optional arrow heads""" if thick: line_character = '=' else: line_character = '-' # follow line to the right end_x, _, line_end_style = self._follow_line(x, y, dx=1, line_character=line_character) # follow line to the left start_x, _, line_start_style = self._follow_line(x, y, dx=-1, line_character=line_character) start_x_fix = end_x_fix = 0 if self.get(start_x - 1, y) == '+': start_x_fix = -0.5 if self.get(end_x + 1, y) == '+': end_x_fix = 0.5 self.tag([(x, y) for x in range(start_x, end_x+1)], CLASS_LINE) # return the new shape object with arrows etc. p1 = complex(self.left(start_x + start_x_fix), self.vcenter(y)) p2 = complex(self.right(end_x + end_x_fix), self.vcenter(y)) shapes = [] if line_start_style: p1, arrow_shapes = line_start_style(p1, p2) shapes.extend(arrow_shapes) if line_end_style: p2, arrow_shapes = line_end_style(p2, p1) shapes.extend(arrow_shapes) shapes.append(Line(p1, p2, thick=thick)) return group(shapes) def _follow_lower_horizontal_line(self, x, y): """find a horizontal line, the line is aligned to the bottom and a bit wider, so that it can be used for shapes like this: ___ __| |___ """ # follow line to the right end_x, _, line_end_style = self._follow_line(x, y, dx=1, line_character='_', arrows=False) # follow line to the left start_x, _, line_start_style = self._follow_line(x, y, dx=-1, line_character='_', arrows=False) self.tag([(x, y) for x in range(start_x, end_x+1)], CLASS_LINE) # return the new shape object with arrows etc. p1 = complex(self.hcenter(start_x-1), self.bottom(y)) p2 = complex(self.hcenter(end_x+1), self.bottom(y)) return [Line(p1, p2)] def _follow_upper_horizontal_line(self, x, y): """find a horizontal line, the line is aligned to the bottom and a bit wider, so that it can be used for shapes like this: |~~~| ~~ ~~~ """ # follow line to the right end_x, _, line_end_style = self._follow_line(x, y, dx=1, line_character='~', arrows=False) # follow line to the left start_x, _, line_start_style = self._follow_line(x, y, dx=-1, line_character='~', arrows=False) self.tag([(x, y) for x in range(start_x, end_x+1)], CLASS_LINE) # return the new shape object with arrows etc. p1 = complex(self.hcenter(start_x-1), self.top(y)) p2 = complex(self.hcenter(end_x+1), self.top(y)) return [Line(p1, p2)] def _follow_line(self, x, y, dx=0, dy=0, line_character=None, arrows=True): """helper function for all the line functions""" # follow line in the given direction while 0 <= x < self.width and 0<= y < self.height and self.get(x+dx, y+dy) == line_character: x += dx y += dy if arrows: # check for arrow head following_character = self.get(x + dx, y + dy) if following_character in self.ARROW_HEADS: line_end_style = self.get_arrow(following_character, dx, dy) if line_end_style: x += dx y += dy else: line_end_style = None else: line_end_style = None return x, y, line_end_style def _plus_joiner(self, x, y): """adjacent '+' signs are connected with a line from center to center required for images like these: +---+ The box should be closed on all sides | +---> and the arrow start should touch the box +---+ """ result = [] #~ for dx, dy in ((1,0), (-1,0), (0,1), (0,-1)): # looking right and down is sufficient as the scan is done from left to # right, top to bottom for dx, dy in ((1, 0), (0, 1)): if self.get(x + dx, y + dy) == '+': result.append(Line( Point(self.hcenter(x), self.vcenter(y)), Point(self.hcenter(x + dx), self.vcenter(y + dy)) )) self.tag([(x, y)], CLASS_JOIN) return result def _follow_fill(self, character, start_x, start_y): """fill shapes like the ones below with a pattern. when the character is upper case, draw a border too. XXX aaa BB XXX a """ fill = self.get_fill(character.upper()) border = character.isupper() result = [] # flood fill algorithm, searching for similar characters coordinates = [] to_scan = [(start_x, start_y)] while to_scan: x, y = to_scan.pop() if self.cls(x, y) is None: if self.get(x, y) == character: result.extend(fill(x, y)) self.tag([(x, y)], CLASS_RECTANGLE) if self.get(x + 1, y) == character: if self.cls(x + 1, y) is None: to_scan.append((x + 1, y)) elif border: result.append(Line( Point(self.right(x), self.top(y)), Point(self.right(x), self.bottom(y)))) if self.get(x - 1, y) == character: if self.cls(x - 1, y) is None: to_scan.append((x - 1, y)) elif border: result.append(Line( Point(self.left(x), self.top(y)), Point(self.left(x), self.bottom(y)))) if self.get(x, y + 1) == character: if self.cls(x, y + 1) is None: to_scan.append((x, y + 1)) elif border: result.append(Line( Point(self.left(x), self.bottom(y)), Point(self.right(x), self.bottom(y)))) if self.get(x, y - 1) == character: if self.cls(x, y - 1) is None: to_scan.append((x, y - 1)) elif border: result.append(Line( Point(self.left(x), self.top(y)), Point(self.right(x), self.top(y)))) return group(result) def _follow_horizontal_string(self, start_x, y, accept_anything=False, quoted=False): """find a string. may contain single spaces, but the detection is aborted after more than one space. Text one "Text two" accept_anything means that all non space characters are interpreted as text. """ # follow line from left to right if quoted: quotation_character = self.get(start_x, y) x = start_x + 1 else: quotation_character = None x = start_x text = [] if self.get(x, y) != ' ': text.append(self.get(x, y)) self.tag([(x, y)], CLASS_STRING) is_first_space = True while 0 <= x + 1 < self.width and self.cls(x + 1, y) is None: if not quoted: if self.get(x + 1, y) == ' ' and not is_first_space: break if not accept_anything and not self.get(x + 1, y).isalnum(): break x += 1 character = self.get(x, y) if character == quotation_character: self.tag([(x, y)], CLASS_STRING) break text.append(character) if character == ' ': is_first_space = False else: is_first_space = True if text[-1] == ' ': del text[-1] x -= 1 self.tag([(x, y) for x in range(start_x, x + 1)], CLASS_STRING) return [Label( Point(self.left(start_x), self.bottom(y)), ''.join(text) )] else: return [] def _follow_rounded_edge(self, x, y): """check for rounded edges: /- | -\- | and also \ / etc. | -/ | \- - | """ result = [] if self.get(x, y) == '/': # rounded rectangles if (self.get(x + 1, y) == '-' and self.get(x, y + 1) == '|'): # upper left corner result.append(Arc( Point(self.hcenter(x), self.bottom(y)), 90, Point(self.right(x), self.vcenter(y)), 180 )) if self.get(x - 1, y) == '-' and self.get(x, y - 1) == '|': # lower right corner result.append(Arc( Point(self.hcenter(x), self.top(y)), -90, Point(self.left(x), self.vcenter(y)), 0 )) if not result: # if used as diagonal line p1 = p2 = None a1 = a2 = 0 arc = c1 = c2 = False if self.get(x + 1, y - 1) == '|': p1 = Point(self.hcenter(x + 1), self.top(y)) a1 = -90 arc = c1 = True elif self.get(x + 1, y - 1) == '+': p1 = Point(self.hcenter(x + 1), self.vcenter(y - 1)) a1 = -135 elif self.get(x + 1, y - 1) == '-': p1 = Point(self.right(x), self.vcenter(y - 1)) a1 = 180 arc = c1 = True elif self.get(x + 1, y - 1) == '/': p1 = Point(self.right(x), self.top(y)) a1 = -135 c1 = True elif self.get(x + 1, y) == '|': p1 = Point(self.hcenter(x + 1), self.top(y)) elif self.get(x, y - 1) == '-': p1 = Point(self.right(x), self.vcenter(y - 1)) if self.get(x - 1, y + 1) == '|': p2 = Point(self.hcenter(x - 1), self.top(y + 1)) a2 = 90 arc = c2 = True elif self.get(x - 1, y + 1) == '+': p2 = Point(self.hcenter(x - 1), self.vcenter(y + 1)) a2 = 45 elif self.get(x - 1, y + 1) == '-': p2 = Point(self.left(x), self.vcenter(y + 1)) a2 = 0 arc = c2 = True elif self.get(x - 1, y + 1) == '/': p2 = Point(self.left(x), self.bottom(y)) a2 = 45 c2 = True elif self.get(x - 1, y) == '|': p2 = Point(self.hcenter(x - 1), self.bottom(y)) elif self.get(x, y + 1) == '-': p2 = Point(self.left(x), self.vcenter(y + 1)) if p1 or p2: if not p1: p1 = Point(self.right(x), self.top(y)) if not p2: p2 = Point(self.left(x), self.bottom(y)) if arc: result.append(Arc(p1, a1, p2, a2, c1, c2)) else: result.append(Line(p1, p2)) else: # '\' # rounded rectangles if self.get(x-1, y) == '-' and self.get(x, y + 1) == '|': # upper right corner result.append(Arc( Point(self.hcenter(x), self.bottom(y)), 90, Point(self.left(x), self.vcenter(y)), 0 )) if self.get(x+1, y) == '-' and self.get(x, y - 1) == '|': # lower left corner result.append(Arc( Point(self.hcenter(x), self.top(y)), -90, Point(self.right(x), self.vcenter(y)), 180 )) if not result: # if used as diagonal line p1 = p2 = None a1 = a2 = 0 arc = c1 = c2 = False if self.get(x - 1, y - 1) == '|': p1 = Point(self.hcenter(x-1), self.top(y)) a1 = -90 arc = c1 = True elif self.get(x - 1, y - 1) == '+': p1 = Point(self.hcenter(x-1), self.vcenter(y - 1)) a1 = -45 elif self.get(x - 1, y - 1) == '-': p1 = Point(self.left(x), self.vcenter(y-1)) a1 = 0 arc = c1 = True elif self.get(x - 1, y - 1) == '\\': p1 = Point(self.left(x), self.top(y)) a1 = -45 c1 = True elif self.get(x - 1, y) == '|': p1 = Point(self.hcenter(x-1), self.top(y)) elif self.get(x, y - 1) == '-': p1 = Point(self.left(x), self.hcenter(y - 1)) if self.get(x + 1, y + 1) == '|': p2 = Point(self.hcenter(x+1), self.top(y + 1)) a2 = 90 arc = c2 = True elif self.get(x + 1, y + 1) == '+': p2 = Point(self.hcenter(x+1), self.vcenter(y + 1)) a2 = 135 elif self.get(x + 1, y + 1) == '-': p2 = Point(self.right(x), self.vcenter(y + 1)) a2 = 180 arc = c2 = True elif self.get(x + 1, y + 1) == '\\': p2 = Point(self.right(x), self.bottom(y)) a2 = 135 c2 = True elif self.get(x + 1, y) == '|': p2 = Point(self.hcenter(x+1), self.bottom(y)) elif self.get(x, y + 1) == '-': p2 = Point(self.right(x), self.vcenter(y + 1)) if p1 or p2: if not p1: p1 = Point(self.left(x), self.top(y)) if not p2: p2 = Point(self.right(x), self.bottom(y)) if arc: result.append(Arc(p1, a1, p2, a2, c1, c2)) else: result.append(Line(p1, p2)) if result: self.tag([(x, y)], CLASS_JOIN) return group(result) def process(input, visitor_class, options=None): """\ Parse input and render using the given visitor class. :param input: String or file like object with the image as text. :param visitor_class: A class object, it will be used to render the resulting image. :param options: A dictionary containing the settings. When ``None`` is given, defaults are used. :returns: instantiated ``visitor_class`` and the image has already been processed with the visitor. :exception: This function can raise an ``UnsupportedFormatError`` exception if the specified format is not supported. """ # remember user options (don't want to rename function parameter above) user_options = options # start with a copy of the defaults options = DEFAULT_OPTIONS.copy() if user_options is not None: # override with settings passed by caller options.update(user_options) if 'fill' not in options or options['fill'] is None: options['fill'] = options['foreground'] # if input is a file like object, read from it (otherwise it is assumed to # be a string) if hasattr(input, 'read'): input = input.read() if options['debug']: sys.stderr.write('%r\n' % (input,)) aaimg = AsciiArtImage(input, options['aspect'], options['textual'], options['widechars']) if options['debug']: sys.stderr.write('%s\n' % (aaimg,)) aaimg.recognize() visitor = visitor_class(options) visitor.visit_image(aaimg) return visitor def render(input, output=None, options=None): """ Render an ASCII art figure to a file or file-like. :param input: If ``input`` is a basestring subclass (str or unicode), the text contained in ``input`` is rendered. If ``input is a file-like object, the text to render is taken using ``input.read()``. :param output: If no ``output`` is specified, the resulting rendered image is returned as a string. If output is a basestring subclass, a file with the name of ``output`` contents is created and the rendered image is saved there. If ``output`` is a file-like object, ``output.write()`` is used to save the rendered image. :param options: A dictionary containing the settings. When ``None`` is given, defaults are used. :returns: This function returns a tuple ``(visitor, output)``, where ``visitor`` is visitor instance that rendered the image and ``output`` is the image as requested by the ``output`` parameter (a ``str`` if it was ``None``, or a file-like object otherwise, which you should ``close()`` if needed). :exception: This function can raise an ``UnsupportedFormatError`` exception if the specified format is not supported. """ if options is None: options = {} close_output = False if output is None: import StringIO options['file_like'] = StringIO.StringIO() elif isinstance(output, basestring): options['file_like'] = file(output, 'wb') close_output = True else: options['file_like'] = output try: # late import of visitor classes to not cause any import errors for # unsupported backends (this would happen when a library a backend # depends on is not installed) if options['format'].lower() == 'svg': import svg visitor_class = svg.SVGOutputVisitor elif options['format'].lower() == 'pdf': import pdf visitor_class = pdf.PDFOutputVisitor elif options['format'].lower() == 'ascii': import aa visitor_class = aa.AsciiOutputVisitor else: # for all other formats, it may be a bitmap type. let # PIL decide if it can write a file of that type. import pil visitor_class = pil.PILOutputVisitor # now render and output the image visitor = process(input, visitor_class, options) finally: if close_output: options['file_like'].close() return (visitor, options['file_like']) def main(): """implement an useful main for use as command line program""" import sys import optparse import os.path parser = optparse.OptionParser( usage = "%prog [options] [file]", version = """\ %prog 0.5 Copyright (C) 2006-2010 aafigure-team Redistribution and use in source and binary forms, with or without modification, are permitted under the terms of the BSD License. THIS SOFTWARE IS PROVIDED BY THE AAFIGURE-TEAM ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AAFIGURE-TEAM BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """, description = "ASCII art to image (SVG, PNG, JPEG, PDF and more) converter." ) parser.add_option("-e", "--encoding", dest = "encoding", action = "store", help = "character encoding of input text", default = DEFAULT_OPTIONS['encoding'], ) parser.add_option("-w", "--wide-chars", dest = "widechars", action = "store", help = "unicode properties to be treated as wide glyph (e.g. 'F,W,A')", default = DEFAULT_OPTIONS['widechars'], ) parser.add_option("-o", "--output", dest = "output", metavar = "FILE", help = "write output to FILE" ) parser.add_option("-t", "--type", dest = "format", help = "filetype: png, jpg, svg (by default autodetect from filename)", default = None, ) parser.add_option("-D", "--debug", dest = "debug", action = "store_true", help = "enable debug outputs", default = DEFAULT_OPTIONS['debug'], ) parser.add_option("-T", "--textual", dest = "textual", action = "store_true", help = "disable horizontal fill detection", default = DEFAULT_OPTIONS['textual'], ) parser.add_option("-s", "--scale", dest = "scale", action = "store", type = 'float', help = "set scale", default = DEFAULT_OPTIONS['scale'], ) parser.add_option("-a", "--aspect", dest = "aspect", action = "store", type = 'float', help = "set aspect ratio", default = DEFAULT_OPTIONS['aspect'], ) parser.add_option("-l", "--linewidth", dest = "line_width", action = "store", type = 'float', help = "set width, svg only", default = DEFAULT_OPTIONS['line_width'], ) parser.add_option("--proportional", dest = "proportional", action = "store_true", help = "use proportional font instead of fixed width", default = DEFAULT_OPTIONS['proportional'], ) parser.add_option("-f", "--foreground", dest = "foreground", action = "store", help = "foreground color default=%default", default = DEFAULT_OPTIONS['foreground'], ) parser.add_option("-x", "--fill", dest = "fill", action = "store", help = "foreground color default=foreground", default = None, ) parser.add_option("-b", "--background", dest = "background", action = "store", help = "foreground color default=%default", default = DEFAULT_OPTIONS['background'], ) parser.add_option("-O", "--option", dest = "_extra_options", action = "append", help = "pass special options to backends (expert user)", ) (options, args) = parser.parse_args() if len(args) > 1: parser.error("too many arguments") if options.format is None: if options.output is None: parser.error("Please specify output format with --type") else: options.format = os.path.splitext(options.output)[1][1:] if args: _input = file(args[0]) else: _input = sys.stdin input = codecs.getreader(options.encoding)(_input) if options.output is None: output = sys.stdout else: output = file(options.output, 'wb') # explicit copying of parameters to the options dictionary options_dict = {} for key in ('widechars', 'textual', 'proportional', 'line_width', 'aspect', 'scale', 'format', 'debug'): options_dict[key] = getattr(options, key) # ensure all color parameters start with a '#' # this is for the convenience of the user as typing the shell comment # character isn't for everyone ;-) for color in ('foreground', 'background', 'fill'): value = getattr(options, color) if value is not None: if value[0] != '#': options_dict[color] = '#%s' % value else: options_dict[color] = value # copy extra options if options._extra_options is not None: for keyvalue in options._extra_options: try: key, value = keyvalue.split('=') except ValueError: parser.error('--option must be in the format = (not %r)' % (keyvalue,)) options_dict[key] = value if options.debug: sys.stderr.write('options=%r\n' % (options_dict,)) try: (visitor, output) = render(input, output, options_dict) output.close() except UnsupportedFormatError, e: print "ERROR: Can't output format '%s': %s" % (options.format, e) # when module is run, run the command line tool if __name__ == '__main__': main() aafigure-0.5/aafigure/svg.py0000644000175000017500000001442711352451604014204 0ustar lchlch"""\ SVG renderer for the aafigure package. (C) 2006 Chris Liechti This is open source software under the BSD license. See LICENSE.txt for more details. """ import sys from xml.sax.saxutils import escape class SVGOutputVisitor: """Render a list of shapes as SVG image.""" def __init__(self, options): self.options = options self.file_like = options['file_like'] self.scale = options['scale']*7 self.line_width = options['line_width'] self.foreground = options['foreground'] self.background = options['background'] self.fillcolor = options['fill'] self.indent = '' # if front is given explicit, use it instead of textual/proportional flags if 'font' in options: self.font = options['font'] else: if options['proportional']: self.font = 'sans-serif' else: self.font = 'monospace' def _num(self, number): """helper to scale numbers for svg output""" return number * self.scale def get_size_attrs(self): """get image size as svg text""" #this function is here beacuse of a hack. the rst2html converter #has to know the size of the figure it inserts return 'width="%s" height="%s"' % ( self._num(self.width), self._num(self.height) ) def visit_image(self, aa_image, xml_header=True): """Process the given ASCIIArtFigure and output the shapes in the SVG file """ self.aa_image = aa_image #save for later XXX not optimal to do it here self.width = (aa_image.width+1)*aa_image.nominal_size*aa_image.aspect_ratio self.height = (aa_image.height+1)*aa_image.nominal_size if xml_header: self.file_like.write("""\ """) else: self.file_like.write("""\n""" % ( self._num(self.width), self._num(self.height) )) self.visit_shapes(aa_image.shapes) self.file_like.write("\n") def visit_shapes(self, shapes): for shape in shapes: shape_name = shape.__class__.__name__.lower() visitor_name = 'visit_%s' % shape_name if hasattr(self, visitor_name): getattr(self, visitor_name)(shape) else: sys.stderr.write("WARNING: don't know how to handle shape %r\n" % shape) # - - - - - - SVG drawing helpers - - - - - - - def _line(self, x1, y1, x2, y2, thick): """Draw a line, coordinates given as four decimal numbers""" self.file_like.write( """%s\n""" % ( self.indent, self._num(x1), self._num(y1), self._num(x2), self._num(y2), self.foreground, self.line_width*(1+bool(thick)))) def _rectangle(self, x1, y1, x2, y2, style=''): """Draw a rectangle, coordinates given as four decimal numbers. ``style`` is inserted in the SVG. It could be e.g. "fill:yellow" """ if x1 > x2: x1, x2 = x2, x1 if y1 > y2: y1, y2 = y2, y1 self.file_like.write("""\ %s """ % ( self.indent, self._num(x1), self._num(y1), self._num(x2-x1), self._num(y2-y1), #~ self.foreground, #stroke:%s; self.fillcolor, #stroke:%s; self.fillcolor, self.line_width, style )) # - - - - - - visitor function for the different shape types - - - - - - - def visit_point(self, point): self.file_like.write("""\ %s """ % ( self.indent, self._num(point.x), self._num(point.y), self._num(0.2), self.foreground, self.foreground, self.line_width)) def visit_line(self, line): x1, x2 = line.start.x, line.end.x y1, y2 = line.start.y, line.end.y self._line(x1, y1, x2, y2, line.thick) def visit_rectangle(self, rectangle): self._rectangle( rectangle.p1.x, rectangle.p1.y, rectangle.p2.x, rectangle.p2.y ) def visit_circle(self, circle): self.file_like.write("""\ %s """ % ( self.indent, self._num(circle.center.x), self._num(circle.center.y), self._num(circle.radius), self.foreground, self.line_width, self.fillcolor)) def visit_label(self, label): # font-weight="bold" style="stroke:%s" self.file_like.write("""\ %s %s %s """ % ( self.indent, self._num(label.position.x), self._num(label.position.y-0.3), # XXX static offset not good in all situations self.font, self._num(self.aa_image.nominal_size), self.foreground, escape(label.text.encode('utf8')), self.indent )) def visit_group(self, group): self.file_like.write("\n") old_indent = self.indent self.indent += ' ' self.visit_shapes(group.shapes) self.indent = old_indent self.file_like.write("\n") def visit_arc(self, arc): p1, p2 = arc.start, arc.end c1 = arc.start_control_point() c2 = arc.end_control_point() self.file_like.write("""\ %s """ % ( self.indent, self._num(p1.x), self._num(p1.y), self._num(c1.x), self._num(c1.y), self._num(c2.x), self._num(c2.y), self._num(p2.x), self._num(p2.y), self.foreground, self.line_width )) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/aafigure/PILhelper.py������������������������������������������������������������������0000644�0001750�0001750�00000003576�11352451604�015234� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""\ Helper objects for bitmap renderer of the aafigure package. (C) 2010 Chris Liechti , Oliver Joos This is open source software under the BSD license. See LICENSE.txt for more details. """ import sys import os from PIL import ImageFont # - - - - - - font helpers - - - - - - - def _find_file(name, top_dir): """Find a file by its name in a directory or sub-directories (recursively). Return absolut path of the file or None if not found. """ for (dirpath, dirnames, filenames) in os.walk(top_dir): if name in filenames: return os.path.join(dirpath, name) return None def font_by_name(name, size): """Get a PIL ImageFont instance by font name and size. If name is not an absolute pathname, it is searched in the default font locations of the underlying OS. If not found, None is returned. """ font = None try: font = ImageFont.truetype(name, size) except IOError: # PIL upto 1.1.7b1 only tries absolute paths for win32 if sys.platform.startswith('linux'): font_path = _find_file(name, '/usr/share/fonts') if font_path: try: font = ImageFont.truetype(font_path, size) except IOError: pass return font def font_by_type(proportional, size): """Get a PIL ImageFont instance by font type and size. If is not True, a mono-spaced font is returned. If no suitable font is found, None is returned. """ if proportional: font = font_by_name('LiberationSans-Regular.ttf', size) if font is None: font = font_by_name('Arial.ttf', size) else: font = font_by_name('LiberationMono-Regular.ttf', size) if font is None: font = font_by_name('Courier_New.ttf', size) return font ����������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/aafigure/shapes.py���������������������������������������������������������������������0000644�0001750�0001750�00000010433�11316525703�014663� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Common shapes for the aafigure package. # # (C) 2009 Chris Liechti # # This is open source software under the BSD license. See LICENSE.txt for more # details. # # This intentionally is no doc comment to make it easier to include the module # in Sphinx ``.. automodule::`` import math def point(object): """return a Point instance. - if object is already a Point instance it's returned as is - complex numbers are converted to Points - a tuple with two elements (x,y) """ if isinstance(object, Point): return object #~ print type(object), object.__class__ if type(object) is complex: return Point(object.real, object.imag) if type(object) is tuple and len(object) == 2: return Point(object[0], object[1]) raise ValueError('can not convert %r to a Point') def group(list_of_shapes): """return a group if the number of shapes is greater than one""" if len(list_of_shapes) > 1: return [Group(list_of_shapes)] else: return list_of_shapes class Point: """A single point. This class primary use is to represent coordinates for the other shapes. """ def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return 'Point(%r, %r)' % (self.x, self.y) def distance(self, other): return math.sqrt( (self.x - other.x)**2 + (self.y - other.y)**2 ) def midpoint(self, other): return Point( (self.x + other.x)/2, (self.y + other.y)/2 ) class Line: """Line with starting and ending point. Both ends can have arrows""" def __init__(self, start, end, thick=False): self.thick = thick self.start = point(start) self.end = point(end) def __repr__(self): return 'Line(%r, %r)' % (self.start, self.end) class Rectangle: """Rectangle with two edge coordinates.""" def __init__(self, p1, p2): self.p1 = point(p1) self.p2 = point(p2) def __repr__(self): return 'Rectangle(%r, %r)' % (self.p1, self.p2) class Circle: """Circle with center coordinates and radius.""" def __init__(self, center, radius): self.center = point(center) self.radius = radius def __repr__(self): return 'Circle(%r, %r)' % (self.center, self.radius) class Label: """A text label at a position""" def __init__(self, position, text): self.position = position self.text = text def __repr__(self): return 'Label(%r, %r)' % (self.position, self.text) class Group: """A group of shapes""" def __init__(self, shapes=None): if shapes is None: shapes = [] self.shapes = shapes def __repr__(self): return 'Group(%r)' % (self.shapes,) class Arc: """A smooth arc between two points""" def __init__(self, start, start_angle, end, end_angle, start_curve=True, end_curve=True): self.start = point(start) self.end = point(end) self.start_angle = start_angle self.end_angle = end_angle self.start_curve = start_curve self.end_curve = end_curve def __repr__(self): return 'Arc(%r, %r, %r, %r, %r, %r)' % (self.start, self.start_angle, self.end, self.end_angle, self.start_curve, self.end_curve) def start_angle_rad(self): return self.start_angle * math.pi / 180 def end_angle_rad(self): return self.end_angle * math.pi / 180 def __tension(self): return self.start.distance( self.end )/3 # assumptions: x increases going right, y increases going down def start_control_point(self): if self.start_curve: dd = self.__tension() angle = self.start_angle_rad() return Point(self.start.x + dd * math.cos(angle), self.start.y - dd * math.sin(angle)) else: return self.start def end_control_point(self): if self.end_curve: dd = self.__tension() angle = self.end_angle_rad() return Point(self.end.x + dd * math.cos(angle), self.end.y - dd * math.sin(angle)) else: return self.end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/aafigure/aa.py�������������������������������������������������������������������������0000644�0001750�0001750�00000006206�11217555227�013770� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""\ Simple ASCII output of the rendered image. Think of it as a low resolution black and white image. (C) 2006 Chris Liechti This is open source software under the BSD license. See LICENSE.txt for more details. """ import sys class AsciiOutputVisitor: """Render a list of shapes as ASCII art. Scaled, think of it as a low resolution black and white image. """ def __init__(self, options): self.options = options self.image = {} self.scale = options['scale'] def visit_image(self, aa_image): self.visit_shapes(aa_image.shapes) self.options['file_like'].write(str(self)) def visit_shapes(self, shapes): for shape in shapes: shape_name = shape.__class__.__name__.lower() visitor_name = 'visit_%s' % shape_name if hasattr(self, visitor_name): getattr(self, visitor_name)(shape) else: sys.stderr.write("WARNING: don't know how to handle shape %r\n" % shape) def visit_group(self, group): self.visit_shapes(group.shapes) def visit_point(self, point): self.image[point.x*self.scale, point.y*self.scale] = '#' def visit_line(self, line): x1, x2 = line.start.x*self.scale, line.end.x*self.scale y1, y2 = line.start.y*self.scale, line.end.y*self.scale if x1 > x2: x1, x2 = x2, x1 if y1 > y2: y1, y2 = y2, y1 dx = x2 - x1 dy = y2 - y1 if dx > dy: y = y1 if dx: m = float(dy)/dx else: m = 0 for x in range(int(x1), int(x2+1)): self.image[x,int(y)] = '#' y += m else: x = x1 if dy: m = float(dx)/dy else: m = 0 for y in range(int(y1), int(y2+1)): self.image[int(x),y] = '#' x += m def visit_rectangle(self, rectangle): x1, x2 = rectangle.p1.x*self.scale, rectangle.p2.x*self.scale y1, y2 = rectangle.p1.y*self.scale, rectangle.p2.y*self.scale if x1 > x2: x1, x2 = x2, x1 if y1 > y2: y1, y2 = y2, y1 for y in range(y1, y2): for x in range(x1, x2): self.image[x,y] = '#' def visit_label(self, label): x, y = label.position.x*self.scale, label.position.y*self.scale for character in label.text: self.image[x, y] = character x += 1 def __str__(self): """return a cropped image""" # find out size min_x = min_y = sys.maxint max_x = max_y = -sys.maxint for x,y in self.image: min_x = min(min_x, x) max_x = max(max_x, x) min_y = min(min_y, y) max_y = max(max_y, y) # render image to lines of text, fill unused fields with a dot result = [] for y in range(min_y, max_y+1): line = [] for x in range(min_x, max_x+1): line.append(self.image.get((x,y), '.')) result.append(''.join(line)) return '%s\n' % '\n'.join(result) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/aafigure/__init__.py�������������������������������������������������������������������0000644�0001750�0001750�00000000325�11217563266�015144� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" AAFigure directive for reStructuredText. This is open source software under the BSD license. See LICENSE.txt for more details. """ from aafigure import process, render, UnsupportedFormatError, AsciiArtImage �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/MANIFEST.in����������������������������������������������������������������������������0000644�0001750�0001750�00000001215�11223504141�012765� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������include README.txt include LICENSE.txt include CHANGES.txt include MANIFEST.in include setup.py include aafigure/*.py include examples/*.py include examples/*.txt include examples/moinmoin/aafig.py include scripts/aafigure include docutils/aafigure_directive.py include docutils/setup-docutils-plugin.py include branding/aafigure-logo.png include branding/aafigure-logo-small.png include branding/aafigure-logo.svg include documentation/appendix.rst include documentation/conf.py include documentation/examples.rst include documentation/index.rst include documentation/Makefile include documentation/manual.rst include documentation/shortintro.rst �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/examples/������������������������������������������������������������������������������0000755�0001750�0001750�00000000000�11352464606�013064� 5����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/examples/README-EXAMPLES.txt�����������������������������������������������������������0000644�0001750�0001750�00000001431�11223461233�015763� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������=================== AAFIGURE EXAMPLES =================== This directory contains a few example input files for the aafigure tool. It also has an example on how to use the module within your own Python programs. Using the ``.txt`` files ======================== The text files can be converted with aafigure, for example:: aafigure fill.txt -o fill.svg aafigure as library =================== ``demo.py`` is a short example on how the module can be used from other Python programs. The MoinMoin plug-in is of course an other example showing this. MoinMoin plug-in ================ The ``moinmoin`` directory contains a Parser plug-in for the MoinMoin wiki. More information can be found in the manual. Hint: copy the file to ``wiki/data/plugin/parser`` and it's ``{{{#!aafig ...``. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/examples/lines.txt���������������������������������������������������������������������0000644�0001750�0001750�00000001244�11214717124�014731� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ---- | ___ ~~~| | -- ___| | === ~~~ + | - + | - + | - + / - / / / / / / / / / / -- |/| / + | | | + + + - - - / / \ - \|/ |\ + + + +-+-+ | + | | | + + + - - - \ \ / - /|\ |/ \ \ \ \ \ \ \ \ \ \ -- |\| \ + | - + | - + | - + \ - + ---> | | | | | | ---< | | | | | | ---o ^ V v o O # ---O ---# ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/examples/moinmoin/���������������������������������������������������������������������0000755�0001750�0001750�00000000000�11352464606�014711� 5����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/examples/moinmoin/aafig.py�������������������������������������������������������������0000777�0001750�0001750�00000006615�11272175025�016344� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#! /usr/bin/env python # -*- coding: iso-8859-1 -*- """ MoinMoin - aafigure This parser is used to visualize aafigure images in the MoinMoin wiki. Usage in wiki pages: {{{#!aafig scale=1.5 foreground=#ff1010 DD o---> }}} @copyright: 2009 by Chris Liechti @license: Simplified BSD License Install: put this file into /data/plugin/parser """ import aafigure from MoinMoin.action import cache def sanitizte_color(value): """clean string for color codes. the sting is inserted directly in the SVG and it must be ensured that the user can not insert arbitrary code""" if len(value) == 7 and value[0] == '#': return "#%06x" % int(value[1:], 16) raise ValueError('invalid color') Dependencies = ["page"] class Parser: """aafigure parser""" extensions = '*.aafigure' def __init__(self, raw, request, **kw): self.pagename = request.page.page_name self.raw = raw self.request = request self.formatter = request.formatter self.args = kw.get('format_args', '') def render(self, formatter): """text to image conversion""" key = 'aafigure_%s' % (cache.key(self.request, itemname=self.pagename, content="%s%s" % (self.raw, self.args)),) if not cache.exists(self.request, key) or not cache.exists(self.request, key+'_size'): # not in cache, regenerate image options = dict(format='svg') for arg in self.args.split(): try: k, v = arg.split('=', 1) except ValueError: # when splitting fails k = arg v = None if k == 'aspect': options['aspect'] = float(v) elif k == 'scale': options['scale'] = float(v) elif k == 'textual': options['textual'] = True elif k == 'proportional': options['proportional'] = True elif k == 'linewidth': options['linewidth'] = float(v) elif k == 'foreground': options['foreground'] = sanitizte_color(v) elif k == 'fill': options['fill'] = sanitizte_color(v) # no 'background' as SVG backend ignores that # no generic options # XXX unknown options are ignored with no message visitor, output = aafigure.render(self.raw, None, options) cache.put(self.request, key, output.getvalue(), content_type="image/svg+xml") # need to store the size attributes too cache.put(self.request, key+'_size', visitor.get_size_attrs(), content_type="text/plain") # get the information from the cache #~ return formatter.image(src=cache.url(self.request, key), alt=xxx) # XXX this currently only works for HTML, obviously... return formatter.rawHTML('' % ( cache.url(self.request, key), cache._get_datafile(self.request, key+'_size').read() # XXX no way to directly read cache? )) def format(self, formatter): """parser output""" self.request.write(self.formatter.div(1, css_class="aafigure")) self.request.write(self.render(formatter)) self.request.write(self.formatter.div(0)) �������������������������������������������������������������������������������������������������������������������aafigure-0.5/examples/sequence.txt������������������������������������������������������������������0000644�0001750�0001750�00000001303�11214716665�015434� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� +---------+ +---------+ +---------+ |Object 1 | |Object 2 | |Object 3 | +----+----+ +----+----+ +----+----+ | | | | | | X | | X----------->X | X X | X<-----------X | X | | X | | X------------------------>X | | X X----------->X X---+ X X X | | | X<--+ X<------------------------X X | | | | | | | | �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/examples/demo.py�����������������������������������������������������������������������0000755�0001750�0001750�00000002566�11217563337�014377� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python """\ Demonstration code for aafigure use as python module. """ import sys import aafigure import aafigure.aa ascii_art = """\ ---> | ^| | <--- | || --+-- <--> | |V | __ __ | |__ +---+ |__| |box| .. +---+ Xenophon """ # Show what we're parsing. print " input ".center(78, '=') print ascii_art # Parse the image. aaimg = aafigure.AsciiArtImage(ascii_art) aaimg.recognize() # For fun, output the ASCII version in the console. print " output ".center(78, '=') aav = aafigure.aa.AsciiOutputVisitor({'file_like':sys.stdout, 'scale':2}) aav.visit_image(aaimg) print "="*78 # Writing an SVG file would be possible in a similar way, but there is the much # easier render function for that. # A stringIO object is returned for the output when the output parameter is not # given. If it were, the output would be directly written to that object. visitor, output = aafigure.render(ascii_art, options={'format':'svg'}) # The process method can be used for a lower level access. The visitor class # has to be specified by the user in this case. To get output, a file like # object has to be passed in the options: # {'file_like' = open("somefile.svg", "wb")} import aafigure.svg import StringIO fl = StringIO.StringIO() visitor = aafigure.process( ascii_art, aafigure.svg.SVGOutputVisitor, options={'file_like': fl} ) ������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/examples/fill.txt����������������������������������������������������������������������0000644�0001750�0001750�00000001060�11214716243�014542� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� A B C D E F G H I J K L M AA BB CC DD EE FF GG HH II JJ KK LL MM AA BB CC DD EE FF GG HH II JJ KK LL MM aa bb cc dd ee ff gg hh ii jj kk ll mm aa bb cc dd ee ff gg hh ii jj kk ll mm N O P Q R S T U V W X Y Z NN OO PP QQ RR SS TT UU VV WW XX YY ZZ NN OO PP QQ RR SS TT UU VV WW XX YY ZZ nn oo pp qq rr ss tt uu vv ww xx yy zz nn oo pp qq rr ss tt uu vv ww xx yy zz ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/docutils/������������������������������������������������������������������������������0000755�0001750�0001750�00000000000�11352464606�013074� 5����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������aafigure-0.5/docutils/setup-docutils-plugin.py������������������������������������������������������0000644�0001750�0001750�00000002616�11214265733�017730� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# $Id$ # Author: Lea Wiemann # Author: Chris Liechti # Copyright: This file has been placed in the public domain. from setuptools import setup, find_packages setup( name = 'docutils-aafigure', version = '0.3', description = "ASCII art figures for reStructuredText", long_description = """\ This package provides a docutils directive that allows to integrate ASCII art figures directly into the text. reST example:: .. aafigure:: +-----+ ^ | | | --->+ +---o---> | | | +-----+ V Please see README.txt for examples. requires docutils (>= 0.5). """, author = 'Chris Liechti', author_email = 'cliechti@gmx.net', install_requires = ['aafigure>=0.2', 'docutils>=0.5'], classifiers = [ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Documentation', 'Topic :: Utilities', ], platforms = 'any', py_modules = [ 'aafigure_directive'], entry_points = { 'docutils.parsers.rst.directives': [ 'aafigure = aafigure_directive:AAFigureDirective' ], }, ) ������������������������������������������������������������������������������������������������������������������aafigure-0.5/docutils/aafigure_directive.py���������������������������������������������������������0000644�0001750�0001750�00000006663�11214265733�017277� 0����������������������������������������������������������������������������������������������������ustar �lch�����������������������������lch��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""\ Implement aafigure directive for docutils. """ import os #~ import cStringIO from aafigure import aafigure from docutils import nodes from docutils.parsers.rst.directives import register_directive, flag DEFAULT_FORMAT = 'svg' #~ DEFAULT_FORMAT = 'png' aafigure_counter = 0 def decode_color(color_string): if color_string[0] == '#': # HTML like color syntax if len(color_string) == 4: # #rgb format r,g,b = [int(c+c, 16) for c in color_string[1:]] elif len(color_string) == 7: # #rrggbb format r,g,b = [int(color_string[n:n+2], 16) for n in range(1, len(color_string), 2)] else: raise ValueError('not a valid color: %r' % color_string) # XXX add a list of named colors return r, g, b def AAFigureDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): text = '\n'.join(content) global aafigure_counter # ensure that options are present and initialized with defaults if not given if not options.has_key('background'): options['background'] = '#ffffff' if not options.has_key('foreground'): options['foreground'] = '#000000' if not options.has_key('fill'): options['fill'] = options['foreground'] # fill = fore by default if not options.has_key('scale'): options['scale'] = 1 if not options.has_key('line_width'): options['line_width'] = 2 if not options.has_key('format'): options['format'] = DEFAULT_FORMAT if not options.has_key('aspect'): options['aspect'] = 1 if not options.has_key('proportional'): options['proportional'] = False if not options.has_key('name'): options['name'] = 'aafigure-%i' % aafigure_counter aafigure_counter += 1 output_name = options['name'] + '.' + options['format'].lower() try: (visitor, output) = aafigure.render(text, output_name, options) except aafigure.UnsupportedFormatError, e: result = [state_machine.reporter.error(str(e), nodes.literal_block(block_text, block_text), line=lineno )] output.close() if options['format'] == 'svg': #~ svgout.visit(aaimg, xml_header = False) # insert data into html using a raw node attributes = {'format': 'html'} #~ # result = [nodes.raw('', '' % ( result = [nodes.raw('', '' '' % (output_name, visitor.get_size_attrs()), **attributes)] #~ result = [nodes.raw('', io.getvalue(), **attributes)] elif options['format'] == 'pdf': # Return a link. wrapper = nodes.generated() wrapper.append(nodes.reference('', '%s (PDF)' % options['name'], refuri=os.path.basename(output_name) )) result = [wrapper] else: # Return an image directive. image_options = {} image_options['uri'] = os.path.basename(output_name) result = [nodes.image(output_name, **image_options)] return result AAFigureDirective.content = True #~ AAFigureDirective.arguments = (1, 1, 1) AAFigureDirective.options = { 'scale': float, 'line_width': float, 'format': str, 'name': str, 'background': str, 'foreground': str, 'fill': str, 'aspect': float, 'textual': flag, 'proportional': flag, } def register(): register_directive('aafigure', AAFigureDirective) aafigure-0.5/setup.py0000644000175000017500000000251711352454063012761 0ustar lchlch# This is a setup script for pythons distutils. It will install the aafigure # extension when run as: python setup.py install # Author: Chris Liechti # # This is open source software under the BSD license. See LICENSE.txt for more # details. from distutils.core import setup setup( name = 'aafigure', version = '0.5', description = "ASCII art to image converter", url = 'http://launchpad.net/aafigure', license = 'BSD', long_description = """\ This package provides a module ``aafigure``, that can be used from other programs, and a command line tool ``aafigure``. Example, test.txt:: +-----+ ^ | | | --->+ +---o---> | | | +-----+ V Command:: aafigure test.txt -t svg -o test.svg Please see README.txt for examples. """, author = 'Chris Liechti', author_email = 'cliechti@gmx.net', classifiers = [ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Documentation', 'Topic :: Utilities', ], platforms = 'any', packages = ['aafigure'], scripts = ['scripts/aafigure'], ) aafigure-0.5/documentation/0000755000175000017500000000000011352464606014117 5ustar lchlchaafigure-0.5/documentation/shortintro.rst0000644000175000017500000001347311222447330017063 0ustar lchlch==================== Short introduction ==================== Docutils & Sphinx integration ============================= In a Sphinx document an image can be inserted like this:: .. aafig:: --> Which results in an image like this: .. aafig:: --> The same contents could also have been placed in a file and then be converted with the aafigure command line tool. Docutils directive ------------------ The ``aafigure`` directive has the following options: - ``:scale: `` enlarge or shrink image - ``:line_width: `` change line with (svg only currently) - ``:format: `` choose backend/output format: 'svg', 'png', all bitmap formats that PIL supports can be used but only few make sense. Line drawings have a good compression and better quality when saved as PNG rather than a JPEG. The best quality will be achieved with SVG, tough not all browsers support this vector image format at this time. - ``:foreground: `` foreground color in the form ``#rgb`` or ``#rrggbb`` - ``:background: `` background color in the form ``#rgb`` or ``#rrggbb`` (*not* for SVG output) - ``:fill: `` fill color in the form ``#rgb`` or ``#rrggbb`` - ``:name: `` use this as filename instead of the automatic generated name - ``:aspect: `` change aspect ratio. Effectively it is the width of the image that is multiplied by this factor. The default setting ``1`` is useful when shapes must have the same look when drawn horizontally or vertically. However, ``:aspect: 0.5`` looks more like the original ASCII and even smaller factors may be useful for timing diagrams and such. But there is a risk that text is cropped or is draw over an object beside it. The stretching is done before drawing arrows or circles, so that they are still good looking. - ``:proportional: `` use a proportional font instead of a mono-spaced one. Sphinx directive ---------------- It is called ``aafig``. The same options as for the Docutils directive apply with the exception of ``format``. That option is not supported as the format is automatically determined. Lines ===== The ``-`` and ``|`` are normally used for lines. ``_`` and ``~`` can also be used. They are slightly longer lines than the ``-``. ``_`` is drawn a bit lower and ``~`` a bit upper. ``=`` gives a thicker line. The later three line types can only be drawn horizontally. :: ---- | ___ ~~~| | -- ___| | === ~~~ .. aafig:: ---- | ___ ~~~| | -- ___| | === ~~~ It is also possible to draw diagonal lines. Their use is somewhat restricted though. Not all cases work as expected. .. aafig:: + | - + | - + | - + / - / / / / / / / / / / -- |/| / + | | | + + + - - - / / \ - \|/ |\ + + + +-+-+ | + | | | + + + - - - \ \ / - /|\ |/ \ \ \ \ \ \ \ \ \ \ -- |\| \ + | - + | - + | - + \ - + And drawing longer diagonal lines with different angles looks ugly... .. aafig:: + | \ / \ / -- Arrows ====== Arrow styles are:: ---> | | | | | | ---< | | | | | | ---o ^ V v o O # ---O ---# .. aafig:: ---> | | | | | | ---< | | | | | | ---o ^ V v o O # ---O ---# Boxes ===== Boxes are automatically draw when the edges are made with ``+``, filled boxes are made with ``X`` (must be at least two units high or wide). It is also possible to make rounded edges in two ways:: +-----+ XXX /--\ -- | | | XXX | | / / +-----+ XXX \--/ | -- .. aafig:: +-----+ XXX /--\ -- | | | XXX | | / / +-----+ XXX \--/ | -- Fills ===== Upper case characters generate shapes with borders, lower case without border. Fills must be at least two characters wide or high. (This reduces the chance that it is detected as Fill instead of a string) .. aafig:: A B C D E F G H I J K L M AA BB CC DD EE FF GG HH II JJ KK LL MM AA BB CC DD EE FF GG HH II JJ KK LL MM aa bb cc dd ee ff gg hh ii jj kk ll mm aa bb cc dd ee ff gg hh ii jj kk ll mm N O P Q R S T U V W X Y Z NN OO PP QQ RR SS TT UU VV WW XX YY ZZ NN OO PP QQ RR SS TT UU VV WW XX YY ZZ nn oo pp qq rr ss tt uu vv ww xx yy zz nn oo pp qq rr ss tt uu vv ww xx yy zz Complex shapes can be filled: .. aafig:: CCCCC C dededede C CCCC CC dededede CC CCCCC dededede Text ==== The images may contain text too. There are different styles to enter text: direct ------ By default are repeated characters detected as fill:: Hello World dd d d .. aafig:: Hello World dd d d quoted ------ Text between quotes has priority over any graphical meaning:: "Hello World" dd d d .. aafig:: "Hello World" dd d d ``"``, ``'`` and ``\``` are all valid quotation marks. The quotes are not visible in the resulting image. This not only disables fills (see below), it also treats ``-``, ``|`` etc. as text. textual option -------------- The ``:textual:`` option disables horizontal fill detection. Fills are only detected when they are vertically at least 2 characters high:: Hello World dd d d .. aafig:: :textual: Hello World dd d d Other ===== :: * { } .. aafig:: * { } aafigure-0.5/documentation/index.rst0000644000175000017500000000067211220263754015760 0ustar lchlch.. aafigure documentation master file, created by sphinx-quickstart on Tue Jun 23 00:11:29 2009. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. aafigure Documentation ====================== .. toctree:: :maxdepth: 2 manual shortintro examples appendix Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` aafigure-0.5/documentation/conf.py0000644000175000017500000001453711352456244015427 0ustar lchlch# -*- coding: utf-8 -*- # # aafigure documentation build configuration file, created by # sphinx-quickstart on Tue Jun 23 00:11:29 2009. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinxcontrib.aafig'] # ensure our local copy of the module is used, not a installed one on the system sys.path.insert(0, '..') # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'aafigure' copyright = u'2010, Chris Liechti' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.5' # The full version, including alpha/beta/rc tags. release = '0.5' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = '../branding/aafigure-logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'aafiguredoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). latex_paper_size = 'a4' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'aafigure.tex', u'aafigure Documentation', u'Chris Liechti', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = '../branding/aafigure-logo.png' # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True aafigure-0.5/documentation/appendix.rst0000644000175000017500000002166111220267620016456 0ustar lchlch========== Appendix ========== API and Implementation Notes ============================ External Interface ------------------ Most users of the module will use one of the following two functions. They provide a high level interface. They are also directly accessible as ``aafigure.process`` respectively ``aafigure.render``. .. module:: aafigure.aafigure .. autofunction:: aafigure.aafigure.process .. autofunction:: aafigure.aafigure.render The command line functionality is implemented in the ``main`` function. .. autofunction:: aafigure.aafigure.main Internal Interface ------------------ The core functionality is implemented in the following class. .. autoclass:: aafigure.aafigure.AsciiArtImage :members: __init__, recognize Images are built using the following shapes. Visitor classes must be able to process these types. .. automodule:: aafigure.shapes :members: Options ------- The ``options`` dictionary is used in a number of places. Valid keys (and their defaults) are: Defining the output: ``file_like`` : use the given file like object to write the output. The object needs to support a ``.write(data)`` method. ``format`` : choose backend/output format: 'svg', 'pdf', 'png' and all bitmap formats that PIL supports can be used but only few make sense. Line drawings have a good compression and better quality when saved as PNG rather than a JPEG. The best quality will be achieved with SVG, tough not all browsers support this vector image format at this time (default: ``'svg'``). Options influencing how an image is parsed: ``textual`` : disables horizontal fill detection. Fills are only detected when they are vertically at least 2 characters high (default: ``False``). ``proportional`` : use a proportional font. Proportional fonts are general better looking than monospace fonts but they can mess the figure if you need them to look as similar as possible to the ASCII art (default: ``False``). Visual properties: ``background`` : background color in the form ``#rgb`` or ``#rrggbb``, *not* for SVG output (default: ``#000000``). ``foreground`` : foreground color in the form ``#rgb`` or ``#rrggbb`` (default: ``#ffffff``). ``fill`` : fill color in the form ``#rgb`` or ``#rrggbb`` (default: same as ``foreground`` color). ``line_width`` : change line with, SVG only currently (default: ``2.0``). ``scale`` : enlarge or shrink image (default: ``1.0``). ``aspect`` : change aspect ratio. Effectively it is the width of the image that is multiplied by this factor. The default setting ``1`` is useful when shapes must have the same look when drawn horizontally or vertically. However, 0.5 looks more like the original ASCII and even smaller factors may be useful for timing diagrams and such. But there is a risk that text is cropped or is drawn over an object besides it. The stretching is done before drawing arrows or circles, so that they are still good looking (default: ``1.0``). Miscellaneous options: ``debug`` : for now, it only prints the original ASCII art figure text (default: ``False``). Visitors -------- A visitor that can be used to render the image must provide the following function (it is called by :func:`process`) .. currentmodule:: your .. class:: Visitor .. method:: visit_image(aa_image) An :class:`AsciiArtImage` instance is passed as parameter. The visiting function needs to implement a loop processing the ``shapes`` attribute. This function must take care of actually outputting the resulting image or it must provide the data in a form useful for the caller (:func:`process` returns the visitor so that the result can be read for example). Example stub class: .. code-block:: python class Visitor: def visit_image(self, aa_image): self.visit_shapes(aa_image.shapes) def visit_shapes(self, shapes): for shape in shapes: shape_name = shape.__class__.__name__.lower() visitor_name = 'visit_%s' % shape_name if hasattr(self, visitor_name): getattr(self, visitor_name)(shape) else: sys.stderr.write("WARNING: don't know how to handle shape %r\n" % shape) def visit_group(self, group): self.visit_shapes(group.shapes) # for actual output implement visitors for all the classes in # aafigure.shapes: def visit_line(self, lineobj): ... def visit_circle(self, circleobj): ... etc... Source tree ----------- The sources can be checked out using bazaar_:: bzr lp:aafigure .. _bazaar: http://bazaar-vcs.org Files in the ``aafigure`` package: ``aafigure.py`` ASCII art parser. This is the main module. ``shapes.py`` Defines a class hierachy for geometric shapes such as lines, circles etc. ``error.py`` Define common exception classes. ``aa.py`` ASCII art output backend. Intended for tests, not really useful for the end user. ``pdf.py`` PDF output backend. Depends on reportlab. ``pil.py`` Bitmap output backend. Using PIL, it can write PNG, JPEG and more formats. ``svg.py`` SVG output backend. Files in the ``docutils`` directory: ``aafigure_directive.py`` Implements the ``aafigure`` Docutils directive that takes these ASCII art figures and generates a drawing. The ``aafigure`` module contains code to parse ASCII art figures and create a list of of shapes. The different output modules can walk through a list of shapes and write image files. TODO ---- - Symbol detection: scan for predefined shapes in the ASCII image and output them as symbol from a library - Symbol libraries for UML, flowchart, electronic schematics, ... - The way the image is embedded is a hack (inserting a tag trough a raw node...) - Search for ways to bring in color. Ideas: - have an :option: to set color tags. Shapes that touch such a tag inherit it's color. The tag would be visible in the ASCII source tough:: .. aafig:: :colortag: 1:red, 2:blue 1---> --->2 - ``:color: x,y,color`` but counting coordinates is no so fun drawback: both are complex to implement, searching for shapes that belong together. It's also not always wanted that e.g. when a line touches a box, both have the same color - aafigure probably needs arguments like ``font-family``, ... - Punctuation not included in strings (now a bit improved but if it has a graphical meaning , then that is chooses, even if it makes no sense), underlines in strings are tricky to detect... - Dotted lines? ``...`` e.g. for ``---...---`` insert a dashed line instead of 3 textual dots. Vertical dashed lines should also work with ``:``. - Group shapes that belong to an object, so that it's easier to import and change the graphics in a vector drawing program. [partly done] - Path optimizer, it happens that many small lines are output where a long line could be used. Authors and Contact =================== - Chris Liechti: original author - Leandro Lucarella: provided many patches The project page is at https://launchpad.net/aafigure It should be used to report bugs and feature requests. License ======= Copyright (c) 2006-2009 aafigure-team All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the aafigure-team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AAFIGURE-TEAM ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AAFIGURE-TEAM BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. aafigure-0.5/documentation/manual.rst0000644000175000017500000001422211222447010016111 0ustar lchlch======== Manual ======== Overview ======== The original idea was to parse ASCII art images, embedded in reST documents and output an image. This would mean that simple illustrations could be embedded as ASCII art in the reST source and still look nice when converted to e.g. HTML. aafigure can be used to write documents that contain drawings in plain text documents and these drawings are converted to appropriate formats for e.g. HTML or PDF versions of the same document. Since then aafigure also grew into a standalone application providing a command line tool for ASCII art to image conversion. ASCII Art --------- The term "ASCII Art" describes a `wide field`_. * (small) drawings found in email signatures * smilies :-) * raster images (this was popular to print images on text only printers a *few* years ago) * simple diagrams using lines, rectangles, arrows aafigure aims to parse the last type of diagrams. .. _`wide field`: http://en.wikipedia.org/wiki/ASCII_art Other text to image tools ------------------------- There are of course also a lot of other tools doing text to image conversions of some sort. One of the main differences is typically that other tools use a description language to generate images from rules. This is a major difference to aafigure which aims to convert good looking diagrams/images etc. in text files to better looking images as bitmap or vector graphics. Graphviz Graphviz is a very popular tool that is excellent for displaying graphs and networks. It does this by reading a list of relations between nodes and it automatically finds the best way to place all the nodes in a visually appealing way. This is quite different from aafigure and both have their strengths. Graphviz is very well suited to document state machines, class hierarchies and other graphs. Installation ============ aafigure -------- This installs a package that can be used from python (``import aafigure``) and a command line script called ``aafigure``. The Python Imaging Library (PIL) needs to be installed when support for bitmap formats is desired and it will need ReportLab for PDF output. To install the module for all users on the system, administrator rights (root) is required.. From source (tar.gz or checkout) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Unpack the archive, enter the ``aafigure-x.y`` directory and run:: python setup.py install Setuptools/PyPI ~~~~~~~~~~~~~~~ Alternatively it can be installed from PyPy, either manually downloading the files and installing as described above or using:: easy_install -U aafigure Packages ~~~~~~~~ There are also packaged versions for some Linux distributions and Windows: Ubuntu Add the repository to ``/etc/apt/sources.list`` as described on this page: https://launchpad.net/~aafigure-team/+archive/ppa Then run ``apt-get update`` and ``apt-get install aafigure`` Arch Linux "aafigure" (or "aafigure-bzr") are found in the category "unsupported". Windows For users that have Python already installed, there is an installer for the extension on http://pypi.python.org/pypi/aafigure Docutils plug-in ---------------- The docutils-aafigure_ extension depends on the aafigure package also requires ``setuptools`` (often packaged as ``python-setuptools``) and Docutils_ itself (0.5 or newer) must be installed. After that, the ``aafigure`` directive will be available. .. _docutils-aafigure: http://pypi.python.org/pypi/aafigure .. _Docutils: http://docutils.sf.net Sphinx plug-in -------------- sphinxcontrib-aafig_ is a plug-in similar to the Docutils_ plug-in, but it automatically selects the image format depending on the output format. XXX elaborate .. _sphinxcontrib-aafig: http://pypi.python.org/pypi/sphinxcontrib-aafig MoinMoin plug-in ---------------- MoinMoin_ is a popular Wiki engine. The plug-in allows to use aafigure drawings within wiki pages. Copy the file ``aafig.py`` from ``examples/moinmoin`` to ``wiki/data/plugin/parser`` of the wiki. The aafigure module itself needs to be installed for the Python version that is used to run MoinMoin_ (see above for instructions). Tested with MoinMoin 1.8. See also: http://moinmo.in/ParserMarket/AaFigure .. _MoinMoin: http://moinmo.in Usage ===== Command line tool ----------------- :: aafigure test.txt -t png -o test.png The tool can also read from standard in and supports many options. Please look at the command's help (or man page):: aafigure --help Within Docutils --------------- A ``aafigure`` directive is provided that allows to insert images:: .. aafigure:: DD o---> :: ./rst2html.py README.txt >README.html This results in the ``README.html`` file and a ``.svg`` file (or the specified file type) for each ``aafigure``. The resulting ``README.html`` file can be viewed with a SVG capable browser. It has been tested with Firefox 1.5, 2.0 and 3.0. Within Sphinx ------------- In ``conf.py`` add:: extensions = ['sphinxcontrib.aafig'] This provides the ``aafig`` directive:: .. aafig:: DD o---> The output format is automatically chosen depending on the generated document format (e.g. HTML or PDF). Within MoinMoin --------------- ASCII Art figures can be inserted into a MoinMoin_ WikiText page the following way:: {{{#!aafig scale=1.5 foreground=#ff1010 DD o---> }}} The parser name is ``aafig`` and options are appended, separated with spaces. Options that require a value take that after a ``=`` without any whitespace between option and value. Supported options are: - ``scale=`` - ``aspect=`` - ``textual`` - ``proportional`` - ``linewidth=`` - ``foreground=#rrggbb`` - ``fill=#rrggbb`` There is no ``background`` as the SVG backend ignores that. And it is not possible to pass generic options. The images are generated and stored in MoinMoin's internal cache. So there is no mess with attached files on the page. Each change on an image generates a new cache entry so the cache may grow over time. However the files can be deleted with no problem as they can be rebuilt when the page is viewed again (the old files are not automatically deleted as they are still used when older revision of a page is displayed). aafigure-0.5/documentation/examples.rst0000644000175000017500000002105211220002750016445 0ustar lchlch========== Examples ========== Simple tests ------------ Different arrow types: .. aafig:: <--> >-> --> <-- >--< o--> -->+<-- o--o o=> Boxes and shapes: .. aafig:: +---------------+ |A box with text| +---------------+ .. aafig:: ---> | ^| | +++ <--- | || --+-- +++ <--> | |V | +++<- __ __ ^ | |__ +---+ |__| | |box| .. +---+ Xenophon Flow chart ---------- .. aafig:: :textual: /---------\ | Start | \----+----/ | V +----+----+ | Init | +----+----+ | +<-----------+ | | V | +----+----+ | | Process | | +----+----+ | | | V | +----+----+ yes | | more? +-------+ +----+----+ | no V /----+----\ | End | \---------/ UML --- No not really, yet. But you get the idea. .. aafig:: :scale: 0.8 +---------+ +---------+ +---------+ |Object 1 | |Object 2 | |Object 3 | +----+----+ +----+----+ +----+----+ | | | | | | X | | X----------->X | X X | X<-----------X | X | | X | | X------------------------>X | | X X----------->X X---+ X X X | | | X<--+ X<------------------------X X | | | | | | | | .. aafig:: :scale: 0.8 +---------+ +---------+ +---------+ | Shape | | Line | | Point | +---------+ +---------+ 2 +---------+ | draw +<--------+ start +----O+ x | | move +<-+ | end | | y | +---------+ \ +---------+ +---------+ \ \ +---------+ +--+ Circle | +---------+ | center | | radius | +---------+ .. aafig:: /-----------\ yes /----------\ -->| then this |--->*--->| and this | + / \-----------/ |no \----------/ /------------\ +-- | | First this |-->+ | \------------/ +-- | + \ /---------\ V /------\ -->| or that |----->*------->| Done | \---------/ \------/ Electronics ----------- It would be cool if it could display simple schematics. .. aafig:: :fill: #fff Iin +-----+ Iout O->---+ R1 +---o-->-----O | +-----+ | | Vin| 100k ----- C1 | Vout | ----- 100n | v | v O---------------o--------O .. - Resistor should not be filled -> can be solved by symbol detection - Capacitor not good, would prefer ``--||--`` -> symbol detection .. aafig:: |/| |\| | | +---+ e| ---+ +--- --+ +-- --+ +-- -+ +- b|/ |\| |/| | | +---+ --+ |\ | | | | c| -+- -+- -+- +++ / \ \ / | | - - -+- -+- -+- | | c\ /e | | | +++ -+- | |b - Diodes OK - Caps not optimal. Too far apart in image, not very good recognisable in ASCII. Space cannot be removed as the two ``+`` signs would be connected otherwise. The schematic below uses an other style. - Arrows in transistor symbols can not be drawn Here is a complete circuit with different parts: .. aafig:: :fill: #fff :scale: 0.8 :textual: Q1 _ 8MHz || || +----+| |+----+ | ||_|| | | | +-----+-------------+-----+ | XIN XOUT | | | | P3.3 +--------------+ SDA/I2C O---+ P2.0 | | | | e| | MSP430F123 | +----+ b|/ V1 SCL/I2C O---+ P2.1 P3.4 +---+ R1 +---+ PNP | | +----+ |\ | IC1 | 1k c| +----+ | | o----+ R3 +---O TXD/RS232 | VCC GND | | +----+ +-----+---------------+---+ | 1k | | | +----+ | | +----+ R2 +---O RXD/RS232 | | +----+ | | 10k GND/I2C O---o-----+----o----------o-----------o--------------------O GND/RS232 | | | C1 | | C2 =+= | ----- 1u | ----- 10u | ----- 5V +---+---+ ----- 16V | | | GND | | D1|/| +----o------+out in+-------o----------o---+ +---O RTS/RS232 | 3V | | |\| +-------+ | IC2 | D2|/| +---+ +---O DTR/RS232 |\| Timing diagrams --------------- .. aafig:: :aspect: 0.5 ^ ___ ___ ____ A |___| |___| |_________| |______ | ___ ___ __ B |_____| |______| |________XX XX__ | +-------------------------------------> t Here is one with descriptions: .. aafig:: SDA edge start stop | | | | v v v v ___ __________ ___ SDA | | | | |____| |_____..._________| ______ _____ _..._ _____ SCL | | | | | | |____| |_____| |_____| ^ ^ ^ ^ ^ ^ | | | | | | | 'sh_in' | 'sh_in' | 'sh_in 'sh_out' 'sh_out' 'sh_out' SCL edge Statistical diagrams -------------------- Benfords_ distribution of the sizes of files on my hard drive: .. _Benfords: http://en.wikipedia.org/wiki/Benfords_law .. aafig:: :foreground: #ff1050 :aspect: 0.7 | 1 +------------------------------------------------------------> 31.59% 2 +-------------------------------> 16.80% 3 +-----------------------> 12.40% 4 +-----------------> 9.31% 5 +--------------> 7.89% 6 +-----------> 6.10% 7 +---------> 5.20% 8 +---------> 4.90% 9 +--------> 4.53% | + | + | + | +---------+---------+---------+---------+---------+---------+---> | + | + | + | 0 5 10 15 20 25 30 Just some bars: .. aafig:: :fill: #00b ^ 2 | EE | 1 EE 4 |DD EE 3 HH |DD EE GG HH |DD EE GG HH +------------------> Schedules --------- .. aafig:: "Week" | 1 | 2 | 3 | 4 | 5 | ------------+---------------------------------------- "Task 1" |HHHH "Task 2" | EEEEEEEEEEEE "Task 3" | GGGGGGGGZZZZZZZZZZ "Task 4" |DD DD DD DD aafigure-0.5/documentation/Makefile0000644000175000017500000000575211277324737015576 0ustar lchlch# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf _build/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html @echo @echo "Build finished. The HTML pages are in _build/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml @echo @echo "Build finished. The HTML pages are in _build/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in _build/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in _build/qthelp, like this:" @echo "# qcollectiongenerator _build/qthelp/aafigure.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile _build/qthelp/aafigure.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex @echo @echo "Build finished; the LaTeX files are in _build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes @echo @echo "The overview file is in _build/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in _build/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in _build/doctest/output.txt." zip: cd _build/html; zip ../../onlinedocs.zip -r * aafigure-0.5/PKG-INFO0000644000175000017500000000172111352464606012344 0ustar lchlchMetadata-Version: 1.0 Name: aafigure Version: 0.5 Summary: ASCII art to image converter Home-page: http://launchpad.net/aafigure Author: Chris Liechti Author-email: cliechti@gmx.net License: BSD Description: This package provides a module ``aafigure``, that can be used from other programs, and a command line tool ``aafigure``. Example, test.txt:: +-----+ ^ | | | --->+ +---o---> | | | +-----+ V Command:: aafigure test.txt -t svg -o test.svg Please see README.txt for examples. Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Documentation Classifier: Topic :: Utilities aafigure-0.5/branding/0000755000175000017500000000000011352464606013032 5ustar lchlchaafigure-0.5/branding/aafigure-logo.svg0000644000175000017500000001743511214731445016301 0ustar lchlch image/svg+xml aafigure DD o---> text to image converter aafigure-0.5/branding/aafigure-logo.png0000644000175000017500000001577611214731504016270 0ustar lchlchPNG  IHDRRlsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<{IDATxyչpEDQbT4W5&54I̪qh4++q0HDE;zن񝞩^jg:3SUg|GT'tx<]'xx'xx'xx'xx'xx'xx'xx'xx'xx'xx'xx'xx'xxMN@W!"| qn-Uէ;5EfWD\prMSՃ;>"Q% 8_8#Yĭ&`yIHC7.H HDdKKcUu] ˁ/T:m% [Ǐ 1ITIi fSu$EΗG$U rxo7!" >ǖ@- U]L"Ll |,{ܠ@=9ZU~c;.*0CUWt`AU[w2@K {q_ʈg0^"aeu gx϶!p֭Z^`;~ސ-bo ?] czƑE*{| ]# ot+~yXv2M VVo1π:Br(n)p/BU\@6SW1+BX8Dd nSi)MnHs4J>UQ>!wZ+2seX @>ైx~^ nX;;G;v S%s?¦Ĝd82`7o]] HUz:LQn*=6*vxm|V)?bo.ʣ.Ur#X/.g.$3t8j,AD( RT"!6uC3KA&~:#0Q)l:#8s2K"2 q `mnȶЯ;\HK"RRw4A/NZ7 o,@LG$>K]y eqP_ L(#'$*W'1 jqXRt0p֌*HT 0#p;6lDs~"bLg6O}UɪUWDwdGTʡZak̲ӊ @D6~rs•ςVU.? )ǖb߲)JJ~|؉%ܠ=xq]9c(*6O+wӋQjV1u^BtƶĸKΧkxi'㹿L ?^!ב ojUAX{xpq':+$l1T8!f|㌇[QU俛nXM* h\N "2[Q.۲r#.s[=âyYө;325t~rh Vf8 Xl F,s8]tEDD*605xܱ -(D/yxʽCֹF߅aZV9e3>&;N g]һ'(1'Ш pWDPU)mQ `нʌg/Kbb`g*d"V kHsDTӀ}NhŠѪz{wQ ܠs{OWҺ=61j+I~-'LMo|Tff׫jx頧$_$/O$bHFlF!fY௚l%9[5[,p5 OPӍ`/c[0L~@l!ئz5E3#2&hٸŸ7Mg+ H$#a4ˊ 恩y.-hZp8Vs%#{`38=@2t7".7q'ZHމIo%#qM H_Ȯ$#%SF:MMiIFΖe ȟ M4lW*QQHFvĬs߁s%#JF6i45c#C1!P2RԊCӺk+|A]m!HFNcM떚]`a$٬PhZghZH22PPӽ@m`.p@"iijw/մbԴ*ss3pddpG2ҝxtjZlV9Frt L$#=ueiWnͳ:[2rp}y| r4Y" (cL.A4MmMlfo"5fHq‘D3{c(HFK|z` 'ciX2rV.t)?B<qaK=\4` @>a27&*RM2ݿC:ٞhZ`TдNiopu1oc]ح{/-%g4k* M$#}5l14lF^!遙R<r>Qzw!^ȗ1HG# ˯4`ݞ_źZK["pbuUV_΁x?kZ_kc0KgYʨEӪ lMkAq*j \ LMn,4&p\jZ.'rvp160S2ruN(-uXwp5?WM~0K`c@۱0 7Yܳ/]wYT?D2ֿcV.&dUdBHF6>sy/aSؤ5ݵ6@;'ӣ 7[,.KJF6Xk"RHFala-QxJG= Au_ڄH{!z@}a.H{AC*#ٞv r>-J`>9`{b,,0V`]ibQ]H P펙=T+^zܹ-stvaݻ?&0D~S74Cu:x3DnyRS}~!ȋ/jەp֮}QZDLS>ԡz+ 6ol\I RKf`veժiADRJz+j_" ,umAL >DFwDS_?HRJyzG*58 8 C*T ]:t"ܕIRoաgYmI^UKgs=&xIP_@n3V̛7)Df8"P,C5^ " Qݭڡ0pܕc[̭l9CeTl.?I^Gv:B gzQNX|H*UFü <䎇f n?UUf7z+W-!Ym?o^#c16ѽCzJCD%֛@dRƫTgcOv ݊lٛ}*;ǸwѻKX{=p?Hߔ`>X11\ɺu};ȱD4W a^S%{UT&Vbkoi#Z?l#yol<`kҳp6 3[|?2t"&fgJaqTЀpaB\͗ll>&~vrQ. (rI3Mv;/NT<n3˽%iț9w بlo{GGg99 ]vw;s`bWl0;ulHNP<c0q{Gc#c-+=v9no}(湘0v̽/sv,\öQ@.v08]?x [8q7 3~ g{cv.#k;SܽSLf26ks$kcqLa3I е,y0w7pg 灞M%7@:p]~w?pQ ǝ]_|+\nw7I}q67Sgx"p`010ȝOƻs۽u0/ P .i轝VJQfQ 9 LPUg3l n!DLӱf%V?Ra/r{w9V%1UDzYl= lUͰ0Njo4/RTy8JDuHb v[??|[9׈vV- `-ZDTMg/a"2[Gz2U7 ڤmۆ.v{œ 3]{ ='`UYMztUuM;.ƞwot@vmXXgzk;>DdVfkE)"1Np]cXoUzn=xfS\xڣkS5.sDoY^Np]'6O"VgB˦}K 5S_HDQU/tV)?c /X0DU/.,8Qz50IDkp̯fe{z7ր`=3p[5XNrz'Oڌѯc?j梸 [VV 29.M_]WcUݽ%޻XKQ sd\6AUzl61z6|3o4l2]8s]ZVczZ,pg2frCUkྊuBx7܊H2"2"lXjzt*P酵 b]?_xMl%E*jbby^*H9pً 4or3sΜ9g̕q:#H:Q& FHeN i ۡJIJIw+&{ET < 4'N(Υǁ} (FI$MuIWJ $}Zҭn4&r-%"|)5^mޟ}6*j"i?*Ic$͔4:b{%}ARq=Fg`33+,v/M 6|00`b5`y2F^w?@7|[ j"iyW7<4s"Q{/> 6"v?gkJ^8$BIׁ &PWzDq|׳>,|33{~ | IM7wḞx|Xpˆ~/Kj3LF@fvA?.vcͬ;oA@z]=~&H7Hc5/Py`i0 |Xz"'H$M.㏗Ft_ flYG>Bx %Gq=MI[Eq (~c a_@58|xIJWW쳅'f 6nd a] rRf) J`OM_+W€MΉ%kLk0Xo~3,J@w 0W|IGN+nj\Cƀiv0~@@̛GmҬ{]e-D7nrj{\`e- bͳet1Im-XR^f(VXUq%g{N-؂>دJjh802\Y /&2)b2VYզXf߳5_s1q[Bkuje53{ACtx2v@6k>c\ely!B(QOluI@-PߵK;z]|BY E\f󋨨X.(]!]Ñ! i]x5qgJ #E G{W[L*ui2fg==F\Hl:0'{ ,0#$H]Ag2aY#lYmԴ& _$v|l)Rl!Ap1ax fInì\?S8 4A7a؅T̂TY; `mm'IT?W7L{9c +# U@UjQEo+ ŸWA@ pn* p'#Fx%m4mMI7(`M8Il30 WoDtƕ=qS ->vp8Ʒ>t ;p_yJ!E9ۀв W9Rq*6:e}r#0M~\