About Shaape

Shaape processes ascii drawings of diagrams and converts them to pixel or vector graphics. The word Shaape itself is a neologism from shape and ascii art.

Why to use Shaape

  • Shaape is consistent between ascii source code and the produced image, What-You-See-Is-What-You-Generate

  • Shaape supports a very natural form of ascii drawing (see the examples below) and attempts to keep the ascii source picture readable

  • Shaape has an extensive feature set while maintaining a small and natural syntax

What is supported

  • arbitrary shapes (e.g. polygons, paths, planar graphs)

    README__1.png
  • arrows (that even can snap to target points)

    README__2.png
  • extensive styles:

    • color

      README__3.png
    • gradients

      README__4.png
    • shadows

      README__5.png
    • transparency

      README__6.png
    • line types: solid, dotted, dashed, dash-dotted

      README__7.png
  • monospaced text in the whole drawing

    README__8.png
  • all objects can be stacked or crossing, z-order is automatically determined

    README__9.png
  • corners can be either miter or round (the * character)

    README__10.png
  • renders to png, svg, eps(experimental), pdf(experimental)

    README__11.png
  • md5sum generation/checking to prevent image generation on unchanged sources

    README__12.png

What will be supported

  • advanced text styles without markup inside the drawing

What will not be supported

  • rotated text: (cannot be displayed in ascii art)

  • circles: (cannot be displayed consistently in ascii art)

Dependencies

  • NetworkX python graph package

  • pycairo

  • pango

  • pangocairo

  • scipy

  • numpy

  • pyyaml

  • networkx

  • setuptools

  • additionally for development:

    • nose

    • coverage

    • mock

Download

Git

$ git clone https://github.com/christiangoltz/shaape.git clones into shaape

PyPi

$ easy_install shaape downloads and installs shaape

Installation

If checked out from Git: To install shaape use:

$ sudo make install

To install the asciidoc filter use:

$ make install-filter

Usage

To run shaape after the installation:

$ shaape

You can also run shaape without any installation by using following line inside the root directory of your git clone:

$ shaape/run.py

Asciidoc integration

Shaape uses an asciidoc block listing definition to integrate into asciidoc:

[shaape]
---------------------------------------------------------------------
    +--------+    +-------------+
    |        |     \           /
    | Hello  |--->  \ Goodbye /
    |   ;)   |      /         \
    |        |     /           \
    +--------+    +-------------+
---------------------------------------------------------------------

Drawing

Lines

The most basic elements of shaape are lines. There are 4 different line elements:

- | \ /

 --------

| \       /
|  \     /
|   \   /
|    \ /

renders:

README__13.png

Text

Text can be directly written into the diagram. Words that contain special characters need to be quoted.

 +---------+
 | foo bar |  +------------+
 +---------+  |'foo|=/baar'|
              +------------+

renders:

README__14.png

Arrows

There are 4 types of arrows supported:

< > ^ v

renders:

README__15.png

Arrows can also be used in connection with lines

<-- --> ^ |   >-- --< | v
        | v           ^ |

renders:

README__16.png

Arrows snap to corners, that are near where they are pointing to:

    +---+   v      <     v
 -->|   |    \    /     >+<
 -->+   |     \  /       ^
    |   |     ^ >
    +---+

renders:

README__17.png

Connectors

The + character is used to connect lines:

  \|/          |
 --+--   +--   +---  ---+---
  /|\   /               |

renders:

README__18.png

If you want to visually connect two lines, without actually connecting them internally, to avoid closing a polygon, you can just directly connect perpendicular lines without the +:

 +----+     +----+
 |    |-----|    |
 |    |-----|    |
 +----+     +----+

renders:

README__19.png

Bezier curves

Bezier curves are drawn using the * character. Shaape tries to match the drawn curve smoothly. Bezier curves can be connected to lines and connectors. The * itself is a similar connector as + and thus can be used to create junctions in special cases too.

 **  **  **  ---+       ****
*      **        \     *
*                 +--**

renders:

README__20.png

Crossings

Crossings are used to let lines go over or under each other without creating a junction between them. You can use brackets and braces to explicitly indicate these junctions via curved lines in the generated image or you can use normal line characters to draw straight crossing lines.

e.g.

   | | | | | |
 +-]-[-(-)-|----+
 | | | | | | |  |
 | | | | | | +--|--
 | | | | | +-------
 | | | | |      |
 | +-+-+-+------~--
 |              |
 +--------------+

renders:

README__21.png

Styles

Although I basically want to disencourage you using fancy colors and styles, there may be some use cases when you need it.

A style defines how an object is drawn. Styles can be defined in a special area below the diagram. This area starts with the identifier options:. On the next line the style description starts. The general syntax for the style description is YAML.

Shaape Identification

To apply a style to an element in the drawing, you need to give it a name. Polygons are named by writing the name into the polygon. Lines are named by writing the name next to them:

                   +---->
  +------+        /
  | box1 |  -----+
  +------+    line1

Style definition

The actual style definition is a yaml list element consisting itself of a dictionary with one element. The key of this element represents the names of the shapes that it should be applied to. The key should always be quoted. It is interpreted as a regular expression and matched against the names of all polygons. Polygons with matching names get this style applied.

options:
 - "boxname[0-9]": {fill: [red, flat], frame: [blue, dashed], no-shadow}

Multiple style application & Style order

If a polygon matches multiple style definitions, then all matching styles are applied sequentially from top to bottom of the style definition. That way you can use a default style for some attributes and change specific attributes for some polygons:

options:
 - ".*": {fill: [red, flat], frame: [blue, dashed], no-shadow}
 - "boxname[0-9]": {fill: [blue]}

Default Styles

Every drawn element matches the regular expression ".*". Thus you can use this expression for the default style. To set the default style for arrows, use "arrow" as key and to set the default style for lines, use "line".

Possible style attributes

The style definition itself contains a yaml dictionary, that may have 3 keys:

fill

defines style properties for filling a polygon, arrow or line

frame

defines the style properties for the frame of a polygon or arrow

text

defines the style properties for text

Fill

Fill may contain:

  • shadow / no-shadow: selects wether the object drops a shadow (default is shadow)

    Example: All shapes without shadow
    - ".*" : {fill : [no-shadow]}
  • solid / dashed / dotted / dash-dotted: selects the line style (only applied to lines, default is solid)

    Example: All lines dotted
    - "_line_" : {fill : [dotted]}
  • multiple color definitions, where a color is:

    • red / green / blue

    • a list with three floats from 0.0 to 1.0, representing RGB (e.g. [0.5, 0.5, 0.5])

    • a list with four floats from 0.0 to 1.0, representing RGBA NOTE: If you provide more than one color, then the polygon will use a gradient for it’s fill.

      Example: Yellow flat fill color
      - ".*" : {fill : [[1, 1, 0]]}
      Example: Gradient from red to green
      - ".*" : {fill : [red, [0, 1, 0]]}
  • a number defining the width of the line, if the fill applies to a line

    Example: Apply width 3.5 to all lines
    - "_line_" : {fill : [3.5]}
Frame

Frame may contain:

  • solid / dashed / dotted / dash-dotted: selects the line style

    Example: All frames dotted
    - ".*" : {frame : [dotted]}
  • a color definition, where a color is:

    • red / green / blue

    • a list with three floats from 0.0 to 1.0, representing RGB (e.g. [0.5, 0.5, 0.5])

      Example: All frames blue
      - ".*" : {frame : [blue]}
  • a number defining the width of the frame line, if the fill applies to a polygon

    Example: Apply width 3.5 to all frames
    - ".*" : {frame : [3.5]}
Text

Text may contain:

  • a font family description in the pango.FontDescription format(see pygtk)

  • a color definition, where a color is:

    • red / green / blue

    • a list with three floats from 0.0 to 1.0, representing RGB

  • shadow / no-shadow: selects wether the object drops a shadow (default is no-shadow)

Example: All text red, italic, Courier, size 9 with shadows
- ".*" : {text : ["Courier italic 9", red, shadow]}
Advanced Examples

shaape/tests/expected_images/example_1.shaape.png

*-----------------------------*
|                             |
|      cyclic dependency      |
|           +-+               |
|     +-----+  \  +-----+     |
|    /    input +  +     \    |
|   /    +--+  /  +--+    \   |
|  /    /   +-+       \    \  |
| +    +               +    + |
| |    |               |    | |
| +    +               +    + |
|  \    \       +-+   /    /  |
|   \    +--+  /  +--+    /   |
|    \     +  + output   /    |
|     +-----+  \  +-----+     |
|               +-+           |
*-----------------------------*

options:
- ".*": {fill:[[0.8, 0.3, 0.3],[0.3, 0.8, 0.3]],frame:[3],text:[[1,1,1]]}
- "input": {fill:[green,blue],frame:[[0.3, 0.2, 0.2, 0.0],1]}
- "output": {fill:[[0, 1, 0, 0.0],[0, 0, 1, 0.4],no-shadow],frame:[[0.3, 0.2, 0.2, 0.0], dotted, 4]}

shaape/tests/expected_images/example_2.shaape.png

           +-------------+
           |Important    |
 ^         |Area         |
m|         |             |
o|         |          6  |
n|         |      5  +-+ |  5   5
e|         |  4  +-+ | | | +-+ +-+
y|  3   3  | +-+ | | | | | | | | |
 | +-+ +-+ | | | | | | | | | | | |
 | | | | | | | | | | | | | | | | |
 | |a| |b| | |b| |a| |b| | |b| |a|
 +-+-+-+-+---+-+-+-+-+-+---+-+-+-+--->
           |             |       time
           +-------------+
options:
- "^a$": {fill:[red],text:[[0, 0, 0, 0]]}
- "^b$": {fill:[blue],text:[[0, 0, 0, 0]]}
- "Important|Area": {fill:[[1, 1, 0, 0.6],[0, 1, 1, 0.2],no-shadow],frame:[[0,0,0,0]],
                     text:["Verdana bold italic 12", [0, 0, 0.5, 0.5], [0.5, 0, 0, 0.5]]}

shaape/tests/expected_images/example_3.shaape.png

  *----------*
 *            *
 |            |
 |  A<----------+
 |            | |
 |       *----+-|---*
 |      *     | |    *
 *      |union* |    |
  *-----+----*  |    |
        |       v    |
        |       B    |
        *            *
         *----------*

options:
- ".*": {frame:[[0, 0, 0, 0], 1]}
- "A": {fill:[[1,1,1,1],[0.4, 0, 0, 1]]}
- "B": {fill:[[0, 0, 0.4, 1],[1,1,1,1]]}
- "union": {fill:[[0.7, 0.2, 0.2, 1], [0.2, 0.2, 0.7, 1], no-shadow]}

shaape/tests/expected_images/feature_z_order2.shaape.png

     +----------+
     | +--+     |
     | |b | a   +--+
     +-|--|-----+  |
       |  |        |
  +----|--|-----+  |
  |c   |  |  +--|--+
  |    |  |  |  |
  +----|--|-----+
       |  |  |
    <--|--|--+
       |  |
       +--+

options:
- "a": {fill:[[1, 0, 0, 0.5]]}
- "b": {fill:[[0, 1, 0, 0.5]]}
- "c": {fill:[[1, 1, 0, 0.5]]}

shaape/tests/expected_images/feature_gradients.shaape.png

+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+
|  1  | |  2  | |  3  | |  4  | |  5  | |  6  | |  7  |
|     | |     | |     | |     | |     | |     | |     |
+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+

options:
- "1" : { fill: [red] }
- "2" : { fill: [red, green] }
- "3" : { fill: [red, green, blue] }
- "4" : { fill: [red, green, blue, red] }
- "5" : { fill: [red, green, blue, red, green] }
- "6" : { fill: [red, green, blue, red, green, blue] }
- "7" : { fill: [red, green, blue, red, green, blue, red] }

shaape/tests/expected_images/example_5.shaape.png

      ^  ^  b  a   v           |        in>---+-----+-----> out1
      |  |  |  |   |           +              |     |
   >--[--[--|--|---|          /|\             |     *----->'out 2'
      |  |  |  |   |lower    +'c'+            |
      +--+--(--(---|         |   |            |    +------>'3'
            |  |   |        'd' 'e'           |   /
            |  +---~--->                      +--+-------->'v'
            |      |
            +------~--->
                   |
                   v

 +-->->->->-+        +---------------------------------------+
 |          |        | +------------+ stacked +------------+ |
 ^ arrowbox v        | | inner1     |         | inner2     | |
 |          | +--+   | | +-------++ +--**     | +--------+ | |
 ^          v |  |   | | |inner3  | |    **-->+ |inner4  | | |
 |          | *  |   | | |        | |         | |        | | |
 +--<-<-<-<-+ *  *   | | |        +-------------+        | | |
              *  *   | | +--------+ |         | +--------+ | |
        +--***   *   | +------------+         +------------+ |
        | flat   *   +---------------------------------------+
        +----****
   /\               *********               +-----+     +-----+
  /  \    +------+ + top     +   ********   |     |\    +--+--+
 /flat\  /      /  |*********|  * shape3 *  |     +-+   +--+--+
 \    / /shape1/   | shape2  |  *        *  |overlap|
  \  / +------+    +         +   ********   +-------+
   \/               *********
options:
- ".*": {fill:[[0.6, 0.8, 0.8], flat], frame:[[0.2, 0.5, 0.4, 1], solid, 2], text:[[0.1, 0.3, 0.2]]}
- "_line_": {fill:[[0.1, 0.1, 0.1], solid, 2]}
- "(flat)|(top)": {fill:[[0.1, 0.1, 0.1, 1], no-shadow, flat], frame:[[0.3, 0.8, 0], dotted, 3], text:[[0.9, 0.9, 0.9]]}
- "arrowbox": {fill:[[0.7, 0.7, 0, 0.5], no-shadow, flat], frame:[[0.3, 0.8, 0], dashed, 3], text:[[0, 0, 0]]}
- "shape[0-9]": {fill:[[0.2, 0.2, 0.2], gradient], frame:[[0.1, 0.5, 0], dotted, 3]}
- "stacked": {fill:[[0.15, 0.3, 0.3], flat], text:[[0, 0, 0]]}
- "inner[34]": {frame:[[0, 0, 0], 1, dotted], text:[[0, 0, 0]]}
- "inner[12]": {fill:[[0.3, 0.6, 0.6], flat], frame:[[0, 0, 0], dashed, 1], text:[[0, 0, 0,]]}
- "a": {fill:[[0.5, 0.0, 0.0, 0.5], 4]}
- "b": {fill:[[0.5, 0.0, 0.5, 0.5], 4]}
- "lower": {fill:[[0.0, 0.5, 0.0, 0.5], 4]}
- "_arrow_": {fill:[[0.5, 0.0, 0.0, 1], flat]}

Copyright (c) 2013, Christian Goltz All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. 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.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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.

The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project.