ASCIIToSVG /

Filename Size Date modified Message
objects
tests
31 B
73.5 KB
1.3 KB
531 B
14.1 KB
a2s
3.4 KB
73.8 KB
5.0 KB
6.6 KB
3.5 KB
339 B
884 B
1.5 KB
8.7 KB
32.6 KB
4.6 KB

ASCIIToSVG

                  .-------------------------.
                  |                         |
                  | .---.-. .-----. .-----. |
                  | | .-. | +-->  | |  <--| |
                  | | '-' | |  <--| +-->  | |
                  | '---'-' '-----' '-----' |
                  |  ascii     2      svg   |
                  |                         |
                  '-------------------------'
                   https://9vx.org/~dho/a2s/

Introduction

License

ASCIIToSVG is copyright © 2012 Devon H. O'Dell and is distributed under an awesome 2-clause BSD license. All code without explicit copyright retains this license. Any code not adhering to this license is explicit in its own copyright.

What does it do?

ASCIIToSVG is a pretty simple PHP library for parsing ASCII art diagrams and converting them to a pretty SVG output format.

But... why?

There are a few reasons, mostly to do with things I really like:

  • I like markdown a lot. If I could write everything in markdown, I probably would.
  • I like ASCII art a lot. I've been playing around with drawing ASCII art since I was both a wee lad and a BBS user.
  • I like pretty pictures. People say they're worth a thousand words.

So I thought, "What if I could combine all these things and start writing markdown documents for my technical designs -- complete with ASCII art diagrams -- that I could then prettify for presentation purposes?"

Aren't there already things that do this?

Well, yes. There is a project called ditaa that has this functionality. Sort of. But it's written in Java and it generates PNG output. I needed something that would generate scalable output, and would be a bit easier to integrate into a wiki (like the one in mtrack, which we use at work). We got it integrated, but I ran into a few bugs and rendering nits that I didn't like. Additionally, it takes a long time to shell out the process and JVM to generate the PNG. If you're doing inline edits on an image, you can imagine that this gets to be a heavy load on both the client and server, just from a data transfer perspective alone.

So I reimplemented it in PHP. There are some things it can do better, and I'm aware of a few rendering bugs already, but so far it works better and is more extensible than ditaa, and I'm enjoying it more.

Building ASCIIToSVG

What?! Building?!?! Isn't this PHP?

Well, yes. But some of it is tool-generated. If you check out the code, you will get the latest generated code, don't worry! This information is more for people who want to contribute and need to make changes to the SVG path parser.

You will need JLexPHP and lemon-php to build the tokenizer and parser for the SVG paths. (This parser was implemented from the SVG 1.1 paths BNF.) JLexPHP requires the JDK to build; lemon-php requires a C compiler.

Simply running make should be enough to get all the autogenerated stuff running. It will also update the logo. Please make sure to view the logo and test files to make sure that you haven't broken any rendering.

Make clean will remove extraneous files that aren't necessary for running; make distclean removes everything autogenerated.

Using ASCIIToSVG

Command Line

A command line utility exists to convert the ASCII file to SVG. You can put this somewhere inside your $PATH and set its permissions to +x. Windows users will have to make their own batch file to run this or remove the first line. Usage:

Usage: a2s [-i[-|filename]] [-o[-|filename]] [-sx-scale,y-scale]
  -h: This usage screen.
  -i: Path to input text file. If unspecified, or set to "-" (hyphen),
      stdin is used.
  -o: Path to output SVG file. If unspecified or set to "-" (hyphen),
      stdout is used.
  -s: Grid scale in pixels. If unspecified, each grid unit on the X
      axis is set to 9 pixels; each grid unit on the Y axis is 16 pixels.

Note that this uses PHP's getopt which handles short options stupidly. You need to put the actual argument right next to the flag; no space. For example: a2s -i- -oout.svg -s10,17 would be a valid command line invocation for this utility.

Class API

There are plenty of comments explaining how everything works (and I'm sure several bugs negating the truth of some of those explanations) in the source file. Basic flow through the API is:

$asciiDiagram = file_get_contents('some_pretty_art');
$o = new org\dh0\a2s\ASCIIToSVG($asciiDiagram);
$o->setDimensionScale(9, 16);
$o->parseGrid();
file_put_contents('some_pretty_art.svg', $o->render());

I'll put more detailed information in the wiki and this README as I have time and inclination to do so.

How Do I Draw?

Enough yammering about the impetus, code, and functionality. I bet you want to draw something. ASCIIToSVG supports a few different ways to do that. If you have more questions, take a look at some of the files in the test subdirectory. (If you have a particularly nice diagram you'd like to see included for demo, feel free to send it my way!)

Basics: Boxes and Lines

Boxes can be polygons of almost any shape (don't try anything too wacky just yet; I haven't). Horizontal, vertical, and diagonal lines are all supported, giving ASCIIToSVG basically complete support for output from App::Asciio. Edges of boxes and lines can be drawn by using the following characters:

  • - or =: Horizontal lines (dash, equals)
  • | or :: Vertical line (pipe, colon)
  • *: Line of ambiguous direction (asterisk)
  • \ or /: Diagonal line (backward slash, forward slash)
  • +: Edge of line passing through a box or line corner (plus)

The existence of a : or = edge on a line causes that line to be rendered as a dashed line.

To draw a box or turn a line, you need to specify corners. The following characters are valid corner characters:

  • ' and .: Quadratic Bézier corners (apostrophe, period)
  • #: Boxed, angular, 90 degree corners for boxes (hash)
  • +: Boxed, angular, 90 degree corners for lines (plus)

The + token is a control point for lines. It denotes an area where a line intersects another line or traverses a box boundary.

A simple box with 3 rounded corners and a line pointing at it:

#----------.
|          | <---------
'----------'

A Quick Note on Diagonals

Diagonal lines cannot be made to form a closed box. If you would like to make diagrams using parallelograms / diamonds (perhaps for flow charts), it is recommended that you annotate regular boxes and use custom objects (see below) instead. Diagonal lines also may not use ' or . smoothed corners.

Basics: Markers

Markers can be attached at the end of a line to give it a nice arrow by using one of the following characters:

  • <: Left marker (less than)
  • >: Right marker (greater than)
  • ^: Up marker (carat)
  • v: Down marker (letter v)

Basics: Text

Text can be inserted at almost any point in the image. Text is rendered in a monospaced font. There aren't many restrictions, but obviously anything you type that looks like it's actually a line or a box is going to be a good candidate for turning into some pretty SVG path. Here is a box with some plain black text in it:

.-------------------------------------.
| Hello here and there and everywhere |
'-------------------------------------'

Basics: Formatting

It's possible to change the format of any boxes / polygons you create. This is done (true to markdown form) by providing a reference on the top left edge of the object, and defining that reference at the bottom of the input. Let me reiterate that the references must be defined at the bottom of the input. An example:

.-------------.  .--------------.
|[Red Box]    |  |[Blue Box]    |
'-------------'  '--------------'

[Red Box]: {"fill":"#aa4444"}
[Blue Box]: {"fill":"#ccccff"}

Text appearing within a stylized box automatically tries to fix the color contrast if the black text would be too dark on the background. The reference commands can take any valid SVG properties / settings for a path element. The commands are specified in JSON form, one per line. Reference commands do not accept nested JSON objects -- don't try to place additional curly braces inside!

The text of a reference is left in the polygon, making it useful as an object title. You can have the reference removed by adding an option a2s:delref to the options JSON object. Any value will suffice to have the reference fully removed from the polygon. You can also replace the text in the label by specifying a2s:label.

This method is (in my opinion) much nicer than the one provided by ditaa:

  • It eats up less space.
  • It fits in smaller boxes.
  • It allows more customization.
  • It's easier to see what formatting changes happened in diffs.
  • It's easier to read any text nested in the box itself.

But that's just me. If you have thoughts on how to do this for lines, please do let me know.

Special References

It is possible to reference an object by its starting row and column. The rows and columns start at (0, 0) in the top left of the diagram. Such references should only be made for stable diagrams, and only if you really need to style text or a line in some particular way. Such references are marked by starting the line with [R,C] where R is the numeric row and C is the numeric column.

Note that boxes start from their top left corner. Text starts from the first non-whitespace character on a line (and only traverses a single space in between text points). Lines start from the left-most point on the line that is not adjoined to any other objects.

Basics: Special Objects

There was some pretty nifty functionality in ditaa for special box types. The ones I was most interested in were the storage and document box, though I recently learned there are more than are advertised on the website. To do this, one adds an a2s:type option to the box and specifies one of the supported object types:

  • storage: The standard "storage" cylinder.
  • document: The standard document box with a wavy bottom.
  • cloud: A network cloud.
  • computer: Something that looks kind of like an iMac.

Creating Special Objects

Although a tutorial on SVG is out of the scope of this document, it is possible to create your own custom objects for your own graphs! Fire up your favorite vector editor that is capable of producing SVG output (if you don't have one, Inkscape will work fine). Draw to your heart's content and save your drawing as an SVG image (you will need to use the path tool; any premade shapes aren't supported). Open the image and extract all the path tags; put them in a file called object.path. Place this file in the objects directory. Any time you reference "a2s:type":"object", your new object will take the place of the box you created.

There are a few caveats to this approach:

  • You must specify the width and height of the original canvas in every path, although there are no minimum or maximum values, nor is there any required aspect ratio.
  • The path (or any group it is in) of the original SVG file must not be transformed in any way. (Inkscape will do this if you change your document size after you start drawing, for instance.)
  • Extraneous tags (i.e. things that aren't <path ... />) will probably break the object parser.
  • Objects are cached in $TMPDIR/.a2s.objcache. If multiple users are using ASCIIToSVG on the same machine, this probably isn't very safe.
  • Reference options are currently only applied to the first path; additional paths are drawn with the default options of the group. This means you probably want your first object to be an outline.

If you're particularly happy with an object and want to share it, feel free to submit it!

External Resources

There are some interesting sites that you might be interested in; these are mildly related to the goals of ASCIIToSVG:

  • ditaa (previously mentioned) is a Java utility with a very similar syntax that translates ASCII diagrams into PNG files.
  • App::Asciio (previously mentioned) allows you to programmatically draw ASCII diagrams.
  • Asciiflow is a web front-end to designing ASCII flowcharts.

If you have something really cool that is related to ASCIIToSVG, and I have failed to list it here, do please let me know.

References

If there's nothing here, you're looking at this README post-markdown-ified.

Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.