ASCIIToSVG
.-------------------------.
| |
| .---.-. .-----. .-----. |
| | .-. | +--> | | <--| |
| | '-' | | <--| +--> | |
| '---'-' '-----' '-----' |
| ascii 2 svg |
| |
'-------------------------'
https://9vx.org/~dho/a2s/
WARNING
I haven't written PHP in years, and this implementation has numerous drawbacks. I recommend you look into using the Go implementation instead. I will continue to accept, review, and merge PRs to this repository, and (where appropriate) mirror any new functionality in the Go version, but I do not intend to identify new bugs, fux old bugs, or self-author new functionality in this repository.
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.
You should have the following structure one your repository:
.
βββ JLexPHP
βββ asciitosvg
βββ lemon-php
Simply running make
on asciitosvg 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. You can also symlink
to it by adding ln -s /Users/asciitosvg/a2s a2s
in /usr/local/bin
.
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:
:::php
$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.