├── .gitignore
├── objects
├── diamond.path
├── document.path
├── storage.path
├── cloud.path
└── computer.path
├── logo.txt
├── Makefile
├── mk52.pl
├── LICENSE
├── tests
└── odd_lines.txt
├── svg-path.lex
├── logo.svg
├── a2s
├── svg-path.y
├── colors.php
├── jlex.php
├── svg-path.lex.php
├── README.md
├── svg-path.php
└── a2s52.php
/.gitignore:
--------------------------------------------------------------------------------
1 | svg-path.h
2 | svg-path.out
3 | .*.swp
4 |
--------------------------------------------------------------------------------
/objects/diamond.path:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/objects/document.path:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/objects/storage.path:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/logo.txt:
--------------------------------------------------------------------------------
1 | .-------------------------.
2 | |[Logo] |
3 | | .---.-. .-----. .-----. |
4 | | | .-. | +--> | | <--+ |
5 | | | '-' | | <--+ +--> | |
6 | | '---'-' '-----' '-----' |
7 | | ascii 2 svg |
8 | | |
9 | '-------------------------'
10 | https://9vx.org/~dho/a2s/
11 |
12 | [Logo]: {"fill":"#88d","a2s:delref":true}
13 |
--------------------------------------------------------------------------------
/objects/cloud.path:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: svg-path.lex svg-path.y
2 | ../lemon-php/lemon -lPHP svg-path.y
3 | java -cp ../jlexphp/JLexPHP.jar JLexPHP.Main svg-path.lex
4 | sed -e 's/Yylex/A2S_Yylex/' -e 's/JLexBase/A2S_JLexBase/' < svg-path.lex.php > .tmp.php
5 | mv .tmp.php svg-path.lex.php
6 | rm -f .tmp.php
7 | ./mk52.pl < ASCIIToSVG.php > a2s52.php
8 | php a2s -ilogo.txt -ologo.svg
9 |
10 | some:
11 | ./mk52.pl < ASCIIToSVG.php > a2s52.php
12 | php a2s -ilogo.txt -ologo.svg
13 |
14 | clean:
15 | rm -f svg-path.h svg-path.out
16 |
17 | distclean:
18 | rm -f svg-path.lex.php svg-path.php svg-path.h svg-path.out a2s52.php
19 |
--------------------------------------------------------------------------------
/objects/computer.path:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mk52.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 | # vim:ts=2:sw=2:et:
3 | # Converts a PHP file with namespaces to a PHP 5.2 compatible form.
4 | # Reads a PHP file from stdin and emits the "fixed" version on stdout
5 | use strict;
6 |
7 | local $/ = undef;
8 | my $src = <>;
9 |
10 | # Comment out namespace specification
11 | $src =~ s/^namespace/#namespace/smg;
12 |
13 |
14 | # Find all class names, but ignore "class" in comments
15 | my $nocomments = $src;
16 | $nocomments =~ s,/\*.*?\*/,,smg;
17 | # For each class, fix the namespace up to a class prefix
18 | while ($nocomments =~ m/class\s+(\S+)/g) {
19 | my $class = $1;
20 | $src =~ s/\b$class\b/A2S_$class/g;
21 | }
22 |
23 | # Get rid of \A2S namespace usage for scanner / parser.
24 | $src =~ s/\\A2S_SVGPathParser/A2S_SVGPathParser/g;
25 | $src =~ s/\\A2S_Yylex/A2S_Yylex/g;
26 |
27 | # Not perfect, since it changes some things in comments in generated output
28 | print $src;
29 |
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright © 2012 Devon H. O'Dell
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | o Redistributions of source code must retain the above copyright notice,
9 | this list of conditions and the following disclaimer.
10 | o Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 | POSSIBILITY OF SUCH DAMAGE.
25 |
26 |
--------------------------------------------------------------------------------
/tests/odd_lines.txt:
--------------------------------------------------------------------------------
1 | blork!
2 | +---+ +---+ ^ ^
3 | | | | | blork! -+| /
4 | --+ +---+ +--> | || /
5 | | <------+----++--> +
6 | .-.---+----. | || |
7 | | | | | v v+------> |
8 | | +---+----+ v
9 | | | v +------------>
10 | | | | .-------+--------.
11 | '-'--------' | some text |
12 | +----------------+
13 | <--. | * more text |
14 | | | * other text |
15 | | | * that's all |
16 | '----------------'
17 | .---------.
18 | | | +------------------------------+
19 | | | | .-------------. | +
20 | '---------' |[1] ^ | | / \
21 | | \ .----' / \
22 | + | \ | / +
23 | / \ #----. \'----. /
24 | / \ | \ | | \ /
25 | \ '----+---' | \ /
26 | \ \ +--+ ----+-----
27 | +------+-----------------+ \ / /
28 | | \ | \ / /
29 | +---- \ + /
30 |
31 |
32 | [1]: {"fill":"#933","a2s:delref":true}
33 |
--------------------------------------------------------------------------------
/svg-path.lex:
--------------------------------------------------------------------------------
1 | [0|1] { return $this->createToken(A2S_SVGPathParser::TK_FLAG); }
17 | [+]?{D}+ { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
18 | "-"{D}+ { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
19 | [+]?{D}*"."{D}+({E})? { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
20 | [+]?{D}+"."{D}*({E})? { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
21 | "-"{D}*"."{D}+({E})? { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
22 | "-"{D}+"."{D}*({E})? { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
23 | [M|m] { return $this->createToken(A2S_SVGPathParser::TK_MCMD); }
24 | [Z|z] { return $this->createToken(A2S_SVGPathParser::TK_ZCMD); }
25 | [L|l] { return $this->createToken(A2S_SVGPathParser::TK_LCMD); }
26 | [H|h] { return $this->createToken(A2S_SVGPathParser::TK_HCMD); }
27 | [V|v] { return $this->createToken(A2S_SVGPathParser::TK_VCMD); }
28 | [Q|q] { return $this->createToken(A2S_SVGPathParser::TK_QCMD); }
29 | [C|c] { return $this->createToken(A2S_SVGPathParser::TK_CCMD); }
30 | [S|s] { return $this->createToken(A2S_SVGPathParser::TK_SCMD); }
31 | [T|t] { return $this->createToken(A2S_SVGPathParser::TK_TCMD); }
32 | [A|a] { return $this->createToken(A2S_SVGPathParser::TK_ACMD); }
33 | [ ,\t\v\n\f\r] { }
34 | . { }
35 |
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
74 |
--------------------------------------------------------------------------------
/a2s:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
6 | * All rights reserved.
7 | *
8 | * Redistribution and use in source and binary forms, with or without
9 | * modification, are permitted provided that the following conditions
10 | * are met:
11 | *
12 | * o Redistributions of source code must retain the above copyright notice,
13 | * this list of conditions and the following disclaimer.
14 | * o Redistributions in binary form must reproduce the above copyright notice,
15 | * this list of conditions and the following disclaimer in the documentation
16 | * and/or other materials provided with the distribution.
17 | *
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | * POSSIBILITY OF SUCH DAMAGE.
29 | */
30 |
31 | namespace org\dh0\a2s;
32 |
33 | require_once 'ASCIIToSVG.php';
34 |
35 | error_reporting(E_ALL|E_STRICT);
36 |
37 | $opts = getopt("bf::hi::o::s::");
38 |
39 | if (isset($opts['h'])) {
40 | echo <<blurDropShadow = false;
114 | }
115 |
116 | if (isset($opts['f'])) {
117 | $o->fontFamily = $opts['f'];
118 | }
119 |
120 | $o->setDimensionScale($scale[0], $scale[1]);
121 | $o->parseGrid();
122 |
123 | file_put_contents($outFile, $o->render());
124 |
125 | /* vim:ts=2:sw=2:et:
126 | * * */
127 |
--------------------------------------------------------------------------------
/svg-path.y:
--------------------------------------------------------------------------------
1 | %name A2S_SVGPath
2 | %token_prefix TK_
3 | %wildcard ANY.
4 |
5 | svg_path ::= moveto_drawto_command_groups.
6 |
7 | moveto_drawto_command_groups ::= moveto_drawto_command_groups moveto_drawto_command_group.
8 | moveto_drawto_command_groups ::= moveto_drawto_command_group.
9 |
10 | moveto_drawto_command_group ::= moveto drawto_commands.
11 |
12 | drawto_commands ::= drawto_commands drawto_command.
13 | drawto_commands ::= drawto_command.
14 |
15 | drawto_command ::= closepath.
16 | drawto_command ::= lineto.
17 | drawto_command ::= horizontal_lineto.
18 | drawto_command ::= vertical_lineto.
19 | drawto_command ::= curveto.
20 | drawto_command ::= smooth_curveto.
21 | drawto_command ::= quadratic_bezier_curveto.
22 | drawto_command ::= smooth_quadratic_bezier_curveto.
23 | drawto_command ::= elliptical_arc.
24 |
25 | moveto ::= MCMD(A) moveto_argument_sequence(B).
26 | {
27 | if (count(B) == 2) {
28 | $this->commands[] = array_merge(array(A), B);
29 | } else {
30 | if (A->value == 'm') {
31 | $arr = array ('value' => 'l');
32 | } else {
33 | $arr = array ('value' => 'L');
34 | }
35 | $c = array_splice(B, 2);
36 | $this->commands[] = array_merge(array(A), B);
37 | $this->commands[] = array_merge(array($arr), $c);
38 | }
39 | }
40 |
41 | moveto_argument_sequence(A) ::= lineto_argument_sequence(B)
42 | coordinate_pair(C). { A = array_merge(B, C); }
43 | moveto_argument_sequence(A) ::= coordinate_pair(B). { A = B; }
44 |
45 | closepath ::= ZCMD(A). { $this->commands[] = array(A); }
46 |
47 | lineto ::= LCMD(A) lineto_argument_sequence(B).
48 | { $this->commands[] = array_merge(array(A), B); }
49 |
50 | lineto_argument_sequence(A) ::= lineto_argument_sequence(B) coordinate_pair(C).
51 | { A = array_merge(B, C); }
52 | lineto_argument_sequence(A) ::= coordinate_pair(B). { A = B; }
53 |
54 | horizontal_lineto ::= HCMD(A) horizontal_lineto_argument_sequence(B).
55 | { $this->commands[] = array_merge(array(A), B); }
56 |
57 | horizontal_lineto_argument_sequence(A) ::=
58 | horizontal_lineto_argument_sequence(B) coordinate(C).
59 | { A = array_merge(B, array(C)); }
60 | horizontal_lineto_argument_sequence(A) ::= coordinate(B). { A = array(B); }
61 |
62 | vertical_lineto ::= VCMD(A) vertical_lineto_argument_sequence(B).
63 | { $this->commands[] = array_merge(array(A), B); }
64 |
65 | vertical_lineto_argument_sequence(A) ::= vertical_lineto_argument_sequence(B)
66 | coordinate(C).
67 | { A = array_merge(B, array(C)); }
68 | vertical_lineto_argument_sequence(A) ::= coordinate(B). { A = array(B); }
69 |
70 | curveto ::= CCMD(A) curveto_argument_sequence(B).
71 | { $this->commands[] = array_merge(array(A), B); }
72 |
73 | curveto_argument_sequence(A) ::= curveto_argument_sequence(B)
74 | curveto_argument(C).
75 | { A = array_merge(B, C); }
76 | curveto_argument_sequence(A) ::= curveto_argument(B). { A = B; }
77 |
78 | curveto_argument(A) ::= coordinate_pair(B) coordinate_pair(C)
79 | coordinate_pair(D).
80 | { A = array_merge(B, C, D); }
81 |
82 | smooth_curveto ::= SCMD(A) smooth_curveto_argument_sequence(B).
83 | { $this->commands[] = array_merge(array(A), B); }
84 |
85 | smooth_curveto_argument_sequence(A) ::= smooth_curveto_argument_sequence(B)
86 | smooth_curveto_argument(C).
87 | { A = array_merge(B, C); }
88 | smooth_curveto_argument_sequence(A) ::= smooth_curveto_argument(B). { A = B; }
89 |
90 | smooth_curveto_argument(A) ::= coordinate_pair(B) coordinate_pair(C).
91 | { A = array_merge(B, C); }
92 |
93 | quadratic_bezier_curveto ::= QCMD(A)
94 | quadratic_bezier_curveto_argument_sequence(B).
95 | { $this->commands[] = array_merge(array(A), B); }
96 |
97 | quadratic_bezier_curveto_argument_sequence(A) ::=
98 | quadratic_bezier_curveto_argument_sequence(B)
99 | quadratic_bezier_curveto_argument(C).
100 | { A = array_merge(B, C); }
101 |
102 | quadratic_bezier_curveto_argument_sequence(A) ::=
103 | quadratic_bezier_curveto_argument(B).
104 | { A = B; }
105 |
106 | quadratic_bezier_curveto_argument(A) ::= coordinate_pair(B) coordinate_pair(C).
107 | { A = array_merge(B, C); }
108 |
109 | smooth_quadratic_bezier_curveto ::= TCMD(A)
110 | smooth_quadratic_bezier_curveto_argument_sequence(B).
111 | { $this->commands[] = array_merge(array(A), B); }
112 |
113 | smooth_quadratic_bezier_curveto_argument_sequence(A) ::=
114 | smooth_quadratic_bezier_curveto_argument_sequence(B)
115 | coordinate_pair(C).
116 | { A = array_merge(B, C); }
117 | smooth_quadratic_bezier_curveto_argument_sequence(A) ::= coordinate_pair(B).
118 | { A = B; }
119 |
120 | elliptical_arc ::= ACMD(A) elliptical_arc_argument_sequence(B).
121 | { $this->commands[] = array_merge(array(A), B); }
122 |
123 | elliptical_arc_argument_sequence(A) ::= elliptical_arc_argument_sequence(B)
124 | elliptical_arc_argument(C).
125 | { A = array_merge(B, C); }
126 | elliptical_arc_argument_sequence(A) ::= elliptical_arc_argument(B).
127 | { A = B; }
128 |
129 | elliptical_arc_argument(A) ::= POSNUM(B) POSNUM(C) number(D) FLAG(E) FLAG(F)
130 | coordinate_pair(G).
131 | { A = array_merge(array(B, C, D, E, F), G); }
132 |
133 | coordinate_pair(A) ::= coordinate(B) coordinate(C). { A = array(B, C); }
134 |
135 | coordinate(A) ::= number(B). { A = B; }
136 |
137 | number(A) ::= POSNUM(B). { A = B; }
138 | number(A) ::= FLAG(B). { A = B; }
139 | number(A) ::= NEGNUM(B). { A = B; }
140 |
141 |
--------------------------------------------------------------------------------
/colors.php:
--------------------------------------------------------------------------------
1 | "#cd5c5c",
5 | "light coral" => "#f08080",
6 | "salmon" => "#fa8072",
7 | "dark salmon" => "#e9967a",
8 | "light salmon" => "#ffa07a",
9 | "crimson" => "#dc143c",
10 | "red" => "#ff0000",
11 | "fire brick" => "#b22222",
12 | "dark red" => "#8b0000",
13 |
14 | "pink" => "#ffc0cb",
15 | "light pink" => "#ffb6c1",
16 | "hot pink" => "#ff69b4",
17 | "deep pink" => "#ff1493",
18 | "medium violet red" => "#c71585",
19 | "pale violet red" => "#db7093",
20 |
21 | "light salmon" => "#ffa07a",
22 | "coral" => "#ff7f50",
23 | "tomato" => "#ff6347",
24 | "orange red" => "#ff4500",
25 | "dark orange" => "#ff8c00",
26 | "orange" => "#ffa500",
27 |
28 | "gold" => "#ffd700",
29 | "yellow" => "#ffff00",
30 | "light yellow" => "#ffffe0",
31 | "lemon chiffon" => "#fffacd",
32 | "light goldenrod yellow" => "#fafad2",
33 | "papaya whip" => "#ffefd5",
34 | "moccasin" => "#ffe4b5",
35 | "peach puff" => "#ffdab9",
36 | "pale goldenrod" => "#eee8aa",
37 | "khaki" => "#f0e68c",
38 | "dark khaki" => "#bdb76b",
39 |
40 | "lavender" => "#e6e6fa",
41 | "thistle" => "#d8bfd8",
42 | "plum" => "#dda0dd",
43 | "violet" => "#ee82ee",
44 | "orchid" => "#da70d6",
45 | "fuchsia" => "#ff00ff",
46 | "magenta" => "#ff00ff",
47 | "medium orchid" => "#ba55d3",
48 | "medium purple" => "#9370db",
49 | "blue violet" => "#8a2be2",
50 | "dark violet" => "#9400d3",
51 | "dark orchid" => "#9932cc",
52 | "dark magenta" => "#8b008b",
53 | "purple" => "#800080",
54 | "indigo" => "#4b0082",
55 | "slate blue" => "#6a5acd",
56 | "dark slate blue" => "#483d8b",
57 |
58 | "green yellow" => "#adff2f",
59 | "chartreuse" => "#7fff00",
60 | "lawn green" => "#7cfc00",
61 | "lime" => "#00ff00",
62 | "lime green" => "#32cd32",
63 | "pale green" => "#98fb98",
64 | "light green" => "#90ee90",
65 | "medium spring green" => "#00fa9a",
66 | "spring green" => "#00ff7f",
67 | "medium sea green" => "#3cb371",
68 | "sea green" => "#2e8b57",
69 | "forest green" => "#228b22",
70 | "green" => "#008000",
71 | "dark green" => "#006400",
72 | "yellow green" => "#9acd32",
73 | "olive drab" => "#6b8e23",
74 | "olive" => "#808000",
75 | "dark olive green" => "#556b2f",
76 | "medium aquamarine" => "#66cdaa",
77 | "dark sea green" => "#8fbc8f",
78 | "light sea green" => "#20b2aa",
79 | "dark cyan" => "#008b8b",
80 | "teal" => "#008080",
81 |
82 | "aqua" => "#00ffff",
83 | "cyan" => "#00ffff",
84 | "light cyan" => "#e0ffff",
85 | "pale turquoise" => "#afeeee",
86 | "aquamarine" => "#7fffd4",
87 | "turquoise" => "#40e0d0",
88 | "medium turquoise" => "#48d1cc",
89 | "dark turquoise" => "#00ced1",
90 | "cadet blue" => "#5f9ea0",
91 | "steel blue" => "#4682b4",
92 | "light steel blue" => "#b0c4de",
93 | "powder blue" => "#b0e0e6",
94 | "light blue" => "#add8e6",
95 | "sky blue" => "#87ceeb",
96 | "light sky blue" => "#87cefa",
97 | "deep sky blue" => "#00bfff",
98 | "dodger blue" => "#1e90ff",
99 | "cornflower blue" => "#6495ed",
100 | "medium slate blue" => "#7b68ee",
101 | "royal blue" => "#4169e1",
102 | "blue" => "#0000ff",
103 | "medium blue" => "#0000cd",
104 | "dark blue" => "#00008b",
105 | "navy" => "#000080",
106 | "midnight blue" => "#191970",
107 |
108 | "cornsilk" => "#fff8dc",
109 | "blanched almond" => "#ffebcd",
110 | "bisque" => "#ffe4c4",
111 | "navajo white" => "#ffdead",
112 | "wheat" => "#f5deb3",
113 | "burly wood" => "#deb887",
114 | "tan" => "#d2b48c",
115 | "rosy brown" => "#bc8f8f",
116 | "sandy brown" => "#f4a460",
117 | "goldenrod" => "#daa520",
118 | "dark goldenrod" => "#b8860b",
119 | "peru" => "#cd853f",
120 | "chocolate" => "#d2691e",
121 | "saddle brown" => "#8b4513",
122 | "sienna" => "#a0522d",
123 | "brown" => "#a52a2a",
124 | "maroon" => "#800000",
125 |
126 | "white" => "#ffffff",
127 | "snow" => "#fffafa",
128 | "honeydew" => "#f0fff0",
129 | "mint cream" => "#f5fffa",
130 | "azure" => "#f0ffff",
131 | "alice blue" => "#f0f8ff",
132 | "ghost white" => "#f8f8ff",
133 | "white smoke" => "#f5f5f5",
134 | "seashell" => "#fff5ee",
135 | "beige" => "#f5f5dc",
136 | "old lace" => "#fdf5e6",
137 | "floral white" => "#fffaf0",
138 | "ivory" => "#fffff0",
139 | "antique white" => "#faebd7",
140 | "linen" => "#faf0e6",
141 | "lavender blush" => "#fff0f5",
142 | "misty rose" => "#ffe4e1",
143 |
144 | "gainsboro" => "#dcdcdc",
145 | "light grey" => "#d3d3d3",
146 | "silver" => "#c0c0c0",
147 | "dark gray" => "#a9a9a9",
148 | "gray" => "#808080",
149 | "dim gray" => "#696969",
150 | "light slate gray" => "#778899",
151 | "slate gray" => "#708090",
152 | "dark slate gray" => "#2f4f4f",
153 | "black" => "#000000"
154 | );
155 |
156 | /* vim:ts=2:sw=2:et:
157 | * * */
158 |
--------------------------------------------------------------------------------
/jlex.php:
--------------------------------------------------------------------------------
1 | line = $line;
36 | $this->col = $col;
37 | $this->value = $value;
38 | $this->type = $type;
39 | }
40 | }
41 |
42 | class A2S_JLexBase {
43 | const YY_F = -1;
44 | const YY_NO_STATE = -1;
45 | const YY_NOT_ACCEPT = 0;
46 | const YY_START = 1;
47 | const YY_END = 2;
48 | const YY_NO_ANCHOR = 4;
49 | const YYEOF = -1;
50 |
51 | protected $YY_BOL;
52 | protected $YY_EOF;
53 |
54 | protected $yy_reader;
55 | protected $yy_buffer;
56 | protected $yy_buffer_read;
57 | protected $yy_buffer_index;
58 | protected $yy_buffer_start;
59 | protected $yy_buffer_end;
60 | protected $yychar = 0;
61 | protected $yycol = 0;
62 | protected $yyline = 0;
63 | protected $yy_at_bol;
64 | protected $yy_lexical_state;
65 | protected $yy_last_was_cr = false;
66 | protected $yy_count_lines = false;
67 | protected $yy_count_chars = false;
68 | protected $yyfilename = null;
69 |
70 | function __construct($stream) {
71 | $this->yy_reader = $stream;
72 | $meta = stream_get_meta_data($stream);
73 | if (!isset($meta['uri'])) {
74 | $this->yyfilename = '<>';
75 | } else {
76 | $this->yyfilename = $meta['uri'];
77 | }
78 |
79 | $this->yy_buffer = "";
80 | $this->yy_buffer_read = 0;
81 | $this->yy_buffer_index = 0;
82 | $this->yy_buffer_start = 0;
83 | $this->yy_buffer_end = 0;
84 | $this->yychar = 0;
85 | $this->yyline = 1;
86 | $this->yy_at_bol = true;
87 | }
88 |
89 | protected function yybegin($state) {
90 | $this->yy_lexical_state = $state;
91 | }
92 |
93 | protected function yy_advance() {
94 | if ($this->yy_buffer_index < $this->yy_buffer_read) {
95 | if (!isset($this->yy_buffer[$this->yy_buffer_index])) {
96 | return $this->YY_EOF;
97 | }
98 | return ord($this->yy_buffer[$this->yy_buffer_index++]);
99 | }
100 | if ($this->yy_buffer_start != 0) {
101 | /* shunt */
102 | $j = $this->yy_buffer_read - $this->yy_buffer_start;
103 | $this->yy_buffer = substr($this->yy_buffer, $this->yy_buffer_start, $j);
104 | $this->yy_buffer_end -= $this->yy_buffer_start;
105 | $this->yy_buffer_start = 0;
106 | $this->yy_buffer_read = $j;
107 | $this->yy_buffer_index = $j;
108 |
109 | $data = fread($this->yy_reader, 8192);
110 | if ($data === false || !strlen($data)) return $this->YY_EOF;
111 | $this->yy_buffer .= $data;
112 | $this->yy_buffer_read .= strlen($data);
113 | }
114 |
115 | while ($this->yy_buffer_index >= $this->yy_buffer_read) {
116 | $data = fread($this->yy_reader, 8192);
117 | if ($data === false || !strlen($data)) return $this->YY_EOF;
118 | $this->yy_buffer .= $data;
119 | $this->yy_buffer_read .= strlen($data);
120 | }
121 | return ord($this->yy_buffer[$this->yy_buffer_index++]);
122 | }
123 |
124 | protected function yy_move_end() {
125 | if ($this->yy_buffer_end > $this->yy_buffer_start &&
126 | $this->yy_buffer[$this->yy_buffer_end-1] == "\n")
127 | $this->yy_buffer_end--;
128 | if ($this->yy_buffer_end > $this->yy_buffer_start &&
129 | $this->yy_buffer[$this->yy_buffer_end-1] == "\r")
130 | $this->yy_buffer_end--;
131 | }
132 |
133 | protected function yy_mark_start() {
134 | if ($this->yy_count_lines || $this->yy_count_chars) {
135 | if ($this->yy_count_lines) {
136 | for ($i = $this->yy_buffer_start; $i < $this->yy_buffer_index; ++$i) {
137 | if ("\n" == $this->yy_buffer[$i] && !$this->yy_last_was_cr) {
138 | ++$this->yyline;
139 | $this->yycol = 0;
140 | }
141 | if ("\r" == $this->yy_buffer[$i]) {
142 | ++$yyline;
143 | $this->yycol = 0;
144 | $this->yy_last_was_cr = true;
145 | } else {
146 | $this->yy_last_was_cr = false;
147 | }
148 | }
149 | }
150 | if ($this->yy_count_chars) {
151 | $this->yychar += $this->yy_buffer_index - $this->yy_buffer_start;
152 | $this->yycol += $this->yy_buffer_index - $this->yy_buffer_start;
153 | }
154 | }
155 | $this->yy_buffer_start = $this->yy_buffer_index;
156 | }
157 |
158 | protected function yy_mark_end() {
159 | $this->yy_buffer_end = $this->yy_buffer_index;
160 | }
161 |
162 | protected function yy_to_mark() {
163 | #echo "yy_to_mark: setting buffer index to ", $this->yy_buffer_end, "\n";
164 | $this->yy_buffer_index = $this->yy_buffer_end;
165 | $this->yy_at_bol = ($this->yy_buffer_end > $this->yy_buffer_start) &&
166 | ("\r" == $this->yy_buffer[$this->yy_buffer_end-1] ||
167 | "\n" == $this->yy_buffer[$this->yy_buffer_end-1] ||
168 | 2028 /* unicode LS */ == $this->yy_buffer[$this->yy_buffer_end-1] ||
169 | 2029 /* unicode PS */ == $this->yy_buffer[$this->yy_buffer_end-1]);
170 | }
171 |
172 | protected function yytext() {
173 | return substr($this->yy_buffer, $this->yy_buffer_start,
174 | $this->yy_buffer_end - $this->yy_buffer_start);
175 | }
176 |
177 | protected function yylength() {
178 | return $this->yy_buffer_end - $this->yy_buffer_start;
179 | }
180 |
181 | static $yy_error_string = array(
182 | 'INTERNAL' => "Error: internal error.\n",
183 | 'MATCH' => "Error: Unmatched input.\n"
184 | );
185 |
186 | protected function yy_error($code, $fatal) {
187 | print self::$yy_error_string[$code];
188 | flush();
189 | if ($fatal) throw new Exception("JLex fatal error " . self::$yy_error_string[$code]);
190 | }
191 |
192 | /* creates an annotated token */
193 | function createToken($type = null) {
194 | if ($type === null) $type = $this->yytext();
195 | $tok = new A2S_JLexToken($type);
196 | $this->annotateToken($tok);
197 | return $tok;
198 | }
199 |
200 | /* annotates a token with a value and source positioning */
201 | function annotateToken(A2S_JLexToken $tok) {
202 | $tok->value = $this->yytext();
203 | $tok->col = $this->yycol;
204 | $tok->line = $this->yyline;
205 | $tok->filename = $this->yyfilename;
206 | }
207 | }
208 |
209 |
--------------------------------------------------------------------------------
/svg-path.lex.php:
--------------------------------------------------------------------------------
1 | yy_lexical_state = self::YYINITIAL;
22 | }
23 |
24 | const YYINITIAL = 0;
25 | static $yy_state_dtrans = array(
26 | 0
27 | );
28 | static $yy_acpt = array(
29 | /* 0 */ self::YY_NOT_ACCEPT,
30 | /* 1 */ self::YY_NO_ANCHOR,
31 | /* 2 */ self::YY_NO_ANCHOR,
32 | /* 3 */ self::YY_NO_ANCHOR,
33 | /* 4 */ self::YY_NO_ANCHOR,
34 | /* 5 */ self::YY_NO_ANCHOR,
35 | /* 6 */ self::YY_NO_ANCHOR,
36 | /* 7 */ self::YY_NO_ANCHOR,
37 | /* 8 */ self::YY_NO_ANCHOR,
38 | /* 9 */ self::YY_NO_ANCHOR,
39 | /* 10 */ self::YY_NO_ANCHOR,
40 | /* 11 */ self::YY_NO_ANCHOR,
41 | /* 12 */ self::YY_NO_ANCHOR,
42 | /* 13 */ self::YY_NO_ANCHOR,
43 | /* 14 */ self::YY_NO_ANCHOR,
44 | /* 15 */ self::YY_NO_ANCHOR,
45 | /* 16 */ self::YY_NO_ANCHOR,
46 | /* 17 */ self::YY_NO_ANCHOR,
47 | /* 18 */ self::YY_NO_ANCHOR,
48 | /* 19 */ self::YY_NO_ANCHOR,
49 | /* 20 */ self::YY_NO_ANCHOR,
50 | /* 21 */ self::YY_NOT_ACCEPT,
51 | /* 22 */ self::YY_NO_ANCHOR,
52 | /* 23 */ self::YY_NO_ANCHOR,
53 | /* 24 */ self::YY_NO_ANCHOR,
54 | /* 25 */ self::YY_NO_ANCHOR,
55 | /* 26 */ self::YY_NO_ANCHOR,
56 | /* 27 */ self::YY_NO_ANCHOR,
57 | /* 28 */ self::YY_NOT_ACCEPT,
58 | /* 29 */ self::YY_NO_ANCHOR,
59 | /* 30 */ self::YY_NOT_ACCEPT,
60 | /* 31 */ self::YY_NO_ANCHOR,
61 | /* 32 */ self::YY_NOT_ACCEPT,
62 | /* 33 */ self::YY_NOT_ACCEPT,
63 | /* 34 */ self::YY_NOT_ACCEPT,
64 | /* 35 */ self::YY_NOT_ACCEPT,
65 | /* 36 */ self::YY_NOT_ACCEPT,
66 | /* 37 */ self::YY_NOT_ACCEPT,
67 | /* 38 */ self::YY_NOT_ACCEPT
68 | );
69 | static $yy_cmap = array(
70 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 18, 18, 19, 18, 18, 19, 19, 19, 19, 19, 19,
71 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 18, 19, 19, 19, 19, 19, 19, 19,
72 | 19, 19, 19, 2, 18, 5, 6, 19, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 19, 19,
73 | 19, 19, 19, 19, 19, 17, 19, 14, 19, 7, 19, 19, 11, 19, 19, 19, 10, 8, 19, 19,
74 | 19, 13, 19, 15, 16, 19, 12, 19, 19, 19, 9, 19, 19, 19, 19, 19, 19, 17, 19, 14,
75 | 19, 7, 19, 19, 11, 19, 19, 19, 10, 8, 19, 19, 19, 13, 19, 15, 16, 19, 12, 19,
76 | 19, 19, 9, 19, 1, 19, 19, 19, 0, 0,);
77 |
78 | static $yy_rmap = array(
79 | 0, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 5, 6, 7,
80 | 8, 9, 3, 10, 11, 12, 13, 14, 15, 9, 16, 1, 17, 11, 18, 19, 12, 13, 14,);
81 |
82 | static $yy_nxt = array(
83 | array(
84 | 1, 2, 3, 22, 4, 23, 29, 31, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 31,
85 |
86 | ),
87 | array(
88 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
89 |
90 | ),
91 | array(
92 | -1, -1, -1, 4, 4, -1, 21, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
93 |
94 | ),
95 | array(
96 | -1, -1, -1, 4, 4, -1, 16, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
97 |
98 | ),
99 | array(
100 | -1, -1, -1, 18, 18, -1, -1, 30, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
101 |
102 | ),
103 | array(
104 | -1, -1, -1, 17, 17, -1, 19, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
105 |
106 | ),
107 | array(
108 | -1, -1, -1, 18, 18, -1, -1, 32, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
109 |
110 | ),
111 | array(
112 | -1, -1, -1, 20, 20, -1, -1, 34, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
113 |
114 | ),
115 | array(
116 | -1, -1, -1, 20, 20, -1, -1, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
117 |
118 | ),
119 | array(
120 | -1, -1, -1, 18, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
121 |
122 | ),
123 | array(
124 | -1, -1, -1, 17, 17, -1, 28, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
125 |
126 | ),
127 | array(
128 | -1, -1, -1, 24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
129 |
130 | ),
131 | array(
132 | -1, -1, -1, 25, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
133 |
134 | ),
135 | array(
136 | -1, -1, -1, 26, 26, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
137 |
138 | ),
139 | array(
140 | -1, -1, -1, 27, 27, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
141 |
142 | ),
143 | array(
144 | -1, -1, -1, 20, 20, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
145 |
146 | ),
147 | array(
148 | -1, -1, 33, 24, 24, 33, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
149 |
150 | ),
151 | array(
152 | -1, -1, 36, 25, 25, 36, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
153 |
154 | ),
155 | array(
156 | -1, -1, 37, 26, 26, 37, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
157 |
158 | ),
159 | array(
160 | -1, -1, 38, 27, 27, 38, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
161 |
162 | ),
163 | );
164 |
165 | public function /*Yytoken*/ nextToken ()
166 | {
167 | $yy_anchor = self::YY_NO_ANCHOR;
168 | $yy_state = self::$yy_state_dtrans[$this->yy_lexical_state];
169 | $yy_next_state = self::YY_NO_STATE;
170 | $yy_last_accept_state = self::YY_NO_STATE;
171 | $yy_initial = true;
172 |
173 | $this->yy_mark_start();
174 | $yy_this_accept = self::$yy_acpt[$yy_state];
175 | if (self::YY_NOT_ACCEPT != $yy_this_accept) {
176 | $yy_last_accept_state = $yy_state;
177 | $this->yy_mark_end();
178 | }
179 | while (true) {
180 | if ($yy_initial && $this->yy_at_bol) $yy_lookahead = self::YY_BOL;
181 | else $yy_lookahead = $this->yy_advance();
182 | $yy_next_state = self::$yy_nxt[self::$yy_rmap[$yy_state]][self::$yy_cmap[$yy_lookahead]];
183 | if ($this->YY_EOF == $yy_lookahead && true == $yy_initial) {
184 | return null;
185 | }
186 | if (self::YY_F != $yy_next_state) {
187 | $yy_state = $yy_next_state;
188 | $yy_initial = false;
189 | $yy_this_accept = self::$yy_acpt[$yy_state];
190 | if (self::YY_NOT_ACCEPT != $yy_this_accept) {
191 | $yy_last_accept_state = $yy_state;
192 | $this->yy_mark_end();
193 | }
194 | }
195 | else {
196 | if (self::YY_NO_STATE == $yy_last_accept_state) {
197 | throw new Exception("Lexical Error: Unmatched Input.");
198 | }
199 | else {
200 | $yy_anchor = self::$yy_acpt[$yy_last_accept_state];
201 | if (0 != (self::YY_END & $yy_anchor)) {
202 | $this->yy_move_end();
203 | }
204 | $this->yy_to_mark();
205 | switch ($yy_last_accept_state) {
206 | case 1:
207 |
208 | case -2:
209 | break;
210 | case 2:
211 | { return $this->createToken(A2S_SVGPathParser::TK_FLAG); }
212 | case -3:
213 | break;
214 | case 3:
215 | { }
216 | case -4:
217 | break;
218 | case 4:
219 | { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
220 | case -5:
221 | break;
222 | case 5:
223 | { return $this->createToken(A2S_SVGPathParser::TK_MCMD); }
224 | case -6:
225 | break;
226 | case 6:
227 | { return $this->createToken(A2S_SVGPathParser::TK_ZCMD); }
228 | case -7:
229 | break;
230 | case 7:
231 | { return $this->createToken(A2S_SVGPathParser::TK_LCMD); }
232 | case -8:
233 | break;
234 | case 8:
235 | { return $this->createToken(A2S_SVGPathParser::TK_HCMD); }
236 | case -9:
237 | break;
238 | case 9:
239 | { return $this->createToken(A2S_SVGPathParser::TK_VCMD); }
240 | case -10:
241 | break;
242 | case 10:
243 | { return $this->createToken(A2S_SVGPathParser::TK_QCMD); }
244 | case -11:
245 | break;
246 | case 11:
247 | { return $this->createToken(A2S_SVGPathParser::TK_CCMD); }
248 | case -12:
249 | break;
250 | case 12:
251 | { return $this->createToken(A2S_SVGPathParser::TK_SCMD); }
252 | case -13:
253 | break;
254 | case 13:
255 | { return $this->createToken(A2S_SVGPathParser::TK_TCMD); }
256 | case -14:
257 | break;
258 | case 14:
259 | { return $this->createToken(A2S_SVGPathParser::TK_ACMD); }
260 | case -15:
261 | break;
262 | case 15:
263 | { }
264 | case -16:
265 | break;
266 | case 16:
267 | { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
268 | case -17:
269 | break;
270 | case 17:
271 | { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
272 | case -18:
273 | break;
274 | case 18:
275 | { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
276 | case -19:
277 | break;
278 | case 19:
279 | { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
280 | case -20:
281 | break;
282 | case 20:
283 | { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
284 | case -21:
285 | break;
286 | case 22:
287 | { return $this->createToken(A2S_SVGPathParser::TK_FLAG); }
288 | case -22:
289 | break;
290 | case 23:
291 | { }
292 | case -23:
293 | break;
294 | case 24:
295 | { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
296 | case -24:
297 | break;
298 | case 25:
299 | { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
300 | case -25:
301 | break;
302 | case 26:
303 | { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
304 | case -26:
305 | break;
306 | case 27:
307 | { return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
308 | case -27:
309 | break;
310 | case 29:
311 | { }
312 | case -28:
313 | break;
314 | case 31:
315 | { }
316 | case -29:
317 | break;
318 | default:
319 | $this->yy_error('INTERNAL',false);
320 | case -1:
321 | }
322 | $yy_initial = true;
323 | $yy_state = self::$yy_state_dtrans[$this->yy_lexical_state];
324 | $yy_next_state = self::YY_NO_STATE;
325 | $yy_last_accept_state = self::YY_NO_STATE;
326 | $this->yy_mark_start();
327 | $yy_this_accept = self::$yy_acpt[$yy_state];
328 | if (self::YY_NOT_ACCEPT != $yy_this_accept) {
329 | $yy_last_accept_state = $yy_state;
330 | $this->yy_mark_end();
331 | }
332 | }
333 | }
334 | }
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ASCIIToSVG ################################################################
2 |
3 | .-------------------------.
4 | | |
5 | | .---.-. .-----. .-----. |
6 | | | .-. | +--> | | <--| |
7 | | | '-' | | <--| +--> | |
8 | | '---'-' '-----' '-----' |
9 | | ascii 2 svg |
10 | | |
11 | '-------------------------'
12 | https://9vx.org/~dho/a2s/
13 |
14 | # WARNING ###################################################################
15 |
16 | I haven't written PHP in years, and this implementation has numerous
17 | drawbacks. I recommend you look into using the
18 | [Go implementation](https://github.com/asciitosvg/asciitosvg) instead. I will
19 | continue to accept, review, and merge PRs to this repository, and (where
20 | appropriate) mirror any new functionality in the Go version, but I do not
21 | intend to identify new bugs, fux old bugs, or self-author new functionality
22 | in this repository.
23 |
24 | ## Introduction #############################################################
25 |
26 | ### License #################################################################
27 |
28 | ASCIIToSVG is copyright © 2012 Devon H. O'Dell and is distributed under an
29 | awesome 2-clause BSD license. All code without explicit copyright retains
30 | this license. Any code not adhering to this license is explicit in its own
31 | copyright.
32 |
33 | ### What does it do? ########################################################
34 |
35 | ASCIIToSVG is a pretty simple PHP library for parsing ASCII art diagrams and
36 | converting them to a pretty SVG output format.
37 |
38 | ### But... why? #############################################################
39 |
40 | There are a few reasons, mostly to do with things I really like:
41 |
42 | * I like markdown a lot. If I could write everything in markdown, I
43 | probably would.
44 | * I like ASCII art a lot. I've been playing around with drawing ASCII art
45 | since I was both a wee lad and a BBS user.
46 | * I like pretty pictures. People say they're worth a thousand words.
47 |
48 | So I thought, "What if I could combine all these things and start writing
49 | markdown documents for my technical designs -- complete with ASCII art
50 | diagrams -- that I could then prettify for presentation purposes?"
51 |
52 | ### Aren't there already things that do this? ###############################
53 |
54 | Well, yes. There is a project called [ditaa][1] that has this functionality.
55 | Sort of. But it's written in Java and it generates PNG output. I needed
56 | something that would generate scalable output, and would be a bit easier to
57 | integrate into a wiki (like the one in [mtrack][2], which we use at work).
58 | We got it integrated, but I ran into a few bugs and rendering nits that I
59 | didn't like. Additionally, it takes a long time to shell out the process
60 | and JVM to generate the PNG. If you're doing inline edits on an image, you
61 | can imagine that this gets to be a heavy load on both the client and
62 | server, just from a data transfer perspective alone.
63 |
64 | So I reimplemented it in PHP. There are some things it can do better, and
65 | I'm aware of a few rendering bugs already, but so far it works better and
66 | is more extensible than ditaa, and I'm enjoying it more.
67 |
68 | ## Building ASCIIToSVG ######################################################
69 |
70 | What?! Building?!?! Isn't this PHP?
71 |
72 | Well, yes. But some of it is tool-generated. If you check out the code, you
73 | will get the latest generated code, don't worry! This information is more
74 | for people who want to contribute and need to make changes to the SVG path
75 | parser.
76 |
77 | You will need [JLexPHP][7] and [lemon-php][8] to build the tokenizer and
78 | parser for the SVG paths. (This parser was implemented [from the SVG 1.1
79 | paths BNF][9].) JLexPHP requires the JDK to build; lemon-php requires a C
80 | compiler.
81 |
82 | You should have the following structure one your repository:
83 |
84 | .
85 | ├── JLexPHP
86 | ├── asciitosvg
87 | └── lemon-php
88 |
89 | Simply running `make` on asciitosvg should be enough to get all the autogenerated
90 | stuff running. It will also update the logo. Please make sure to view the logo and
91 | test files to make sure that you haven't broken any rendering.
92 |
93 | Make clean will remove extraneous files that aren't necessary for running;
94 | make distclean removes everything autogenerated.
95 |
96 | ## Using ASCIIToSVG #########################################################
97 |
98 | ### Command Line ############################################################
99 |
100 | A command line utility exists to convert the ASCII file to SVG. You can put
101 | this somewhere inside your `$PATH` and set its permissions to +x. You can also symlink
102 | to it by adding `ln -s /Users/asciitosvg/a2s a2s` in `/usr/local/bin`.
103 | Windows users will have to make their own batch file to run this or remove the first
104 | line. Usage:
105 |
106 | Usage: a2s [-i[-|filename]] [-o[-|filename]] [-sx-scale,y-scale]
107 | -h: This usage screen.
108 | -i: Path to input text file. If unspecified, or set to "-" (hyphen),
109 | stdin is used.
110 | -o: Path to output SVG file. If unspecified or set to "-" (hyphen),
111 | stdout is used.
112 | -s: Grid scale in pixels. If unspecified, each grid unit on the X
113 | axis is set to 9 pixels; each grid unit on the Y axis is 16 pixels.
114 |
115 | Note that this uses PHP's `getopt` which handles short options stupidly. You
116 | need to put the actual argument right next to the flag; no space. For
117 | example: `a2s -i- -oout.svg -s10,17` would be a valid command line
118 | invocation for this utility.
119 |
120 | ### Class API ###############################################################
121 |
122 | There are plenty of comments explaining how everything works (and I'm sure
123 | several bugs negating the truth of some of those explanations) in the source
124 | file. Basic flow through the API is:
125 |
126 | :::php
127 | $asciiDiagram = file_get_contents('some_pretty_art');
128 | $o = new org\dh0\a2s\ASCIIToSVG($asciiDiagram);
129 | $o->setDimensionScale(9, 16);
130 | $o->parseGrid();
131 | file_put_contents('some_pretty_art.svg', $o->render());
132 |
133 | I'll put more detailed information in the [wiki][3] and this `README` as I
134 | have time and inclination to do so.
135 |
136 | ## How Do I Draw? ###########################################################
137 |
138 | Enough yammering about the impetus, code, and functionality. I bet you want
139 | to draw something. ASCIIToSVG supports a few different ways to do that. If
140 | you have more questions, take a look at some of the files in the `test`
141 | subdirectory. (If you have a particularly nice diagram you'd like to see
142 | included for demo, feel free to send it my way!)
143 |
144 | ### Basics: Boxes and Lines #################################################
145 |
146 | Boxes can be polygons of almost any shape (don't try anything too wacky
147 | just yet; I haven't). Horizontal, vertical, and diagonal lines are all
148 | supported, giving ASCIIToSVG basically complete support for output from
149 | [App::Asciio][4]. Edges of boxes and lines can be drawn by using the
150 | following characters:
151 |
152 | * `-` or `=`: Horizontal lines (dash, equals)
153 | * `|` or `:`: Vertical line (pipe, colon)
154 | * `*`: Line of ambiguous direction (asterisk)
155 | * `\` or `/`: Diagonal line (backward slash, forward slash)
156 | * `+`: Edge of line passing through a box or line corner (plus)
157 |
158 | The existence of a `:` or `=` edge on a line causes that line to be
159 | rendered as a dashed line.
160 |
161 | To draw a box or turn a line, you need to specify corners. The following
162 | characters are valid corner characters:
163 |
164 | * `'` and `.`: Quadratic Bézier corners (apostrophe, period)
165 | * `#`: Boxed, angular, 90 degree corners for boxes (hash)
166 | * `+`: Boxed, angular, 90 degree corners for lines (plus)
167 |
168 | The `+` token is a control point for lines. It denotes an area where a line
169 | intersects another line or traverses a box boundary.
170 |
171 | A simple box with 3 rounded corners and a line pointing at it:
172 |
173 | #----------.
174 | | | <---------
175 | '----------'
176 |
177 | #### A Quick Note on Diagonals ##############################################
178 |
179 | Diagonal lines cannot be made to form a closed box. If you would like to
180 | make diagrams using parallelograms / diamonds (perhaps for flow charts), it
181 | is recommended that you annotate regular boxes and use custom objects (see
182 | below) instead. Diagonal lines also may not use `'` or `.` smoothed corners.
183 |
184 | ### Basics: Markers #########################################################
185 |
186 | Markers can be attached at the end of a line to give it a nice arrow by
187 | using one of the following characters:
188 |
189 | * `<`: Left marker (less than)
190 | * `>`: Right marker (greater than)
191 | * `^`: Up marker (carat)
192 | * `v`: Down marker (letter v)
193 |
194 | ### Basics: Text ############################################################
195 |
196 | Text can be inserted at almost any point in the image. Text is rendered in
197 | a monospaced font. There aren't many restrictions, but obviously anything
198 | you type that looks like it's actually a line or a box is going to be a good
199 | candidate for turning into some pretty SVG path. Here is a box with some
200 | plain black text in it:
201 |
202 | .-------------------------------------.
203 | | Hello here and there and everywhere |
204 | '-------------------------------------'
205 |
206 | ### Basics: Formatting #####################################################
207 |
208 | It's possible to change the format of any boxes / polygons you create. This
209 | is done (true to markdown form) by providing a reference on the top left
210 | edge of the object, and defining that reference at the bottom of the input.
211 | Let me reiterate that the references *must* be defined at the *bottom* of
212 | the input. An example:
213 |
214 | .-------------. .--------------.
215 | |[Red Box] | |[Blue Box] |
216 | '-------------' '--------------'
217 |
218 | [Red Box]: {"fill":"#aa4444"}
219 | [Blue Box]: {"fill":"#ccccff"}
220 |
221 | Text appearing within a stylized box automatically tries to fix the color
222 | contrast if the black text would be too dark on the background. The
223 | reference commands can take any valid SVG properties / settings for a
224 | [path element][5]. The commands are specified in JSON form, one per line.
225 | Reference commands do not accept nested JSON objects -- don't try to
226 | place additional curly braces inside!
227 |
228 | The text of a reference is left in the polygon, making it useful as an
229 | object title. You can have the reference removed by adding an option
230 | `a2s:delref` to the options JSON object. Any value will suffice to have
231 | the reference fully removed from the polygon. You can also replace the
232 | text in the label by specifying `a2s:label`.
233 |
234 | This method is (in my opinion) much nicer than the one provided by ditaa:
235 |
236 | * It eats up less space.
237 | * It fits in smaller boxes.
238 | * It allows more customization.
239 | * It's easier to see what formatting changes happened in diffs.
240 | * It's easier to read any text nested in the box itself.
241 |
242 | But that's just me. If you have thoughts on how to do this for lines,
243 | please do let me know.
244 |
245 | #### Special References #####################################################
246 |
247 | It is possible to reference an object by its starting row and column. The
248 | rows and columns start at (0, 0) in the top left of the diagram. Such
249 | references should only be made for stable diagrams, and only if you *really*
250 | need to style text or a line in some particular way. Such references are
251 | marked by starting the line with `[R,C]` where `R` is the numeric row and
252 | `C` is the numeric column.
253 |
254 | Note that boxes start from their top left corner. Text starts from the first
255 | non-whitespace character on a line (and only traverses a single space in
256 | between text points). Lines start from the left-most point on the line that
257 | is not adjoined to any other objects.
258 |
259 | ### Basics: Special Objects #################################################
260 |
261 | There was some pretty nifty functionality in ditaa for special box types.
262 | The ones I was most interested in were the `storage` and `document` box,
263 | though I recently learned there are more than are advertised on the website.
264 | To do this, one adds an `a2s:type` option to the box and specifies one of
265 | the supported object types:
266 |
267 | * `storage`: The standard "storage" cylinder.
268 | * `document`: The standard document box with a wavy bottom.
269 | * `cloud`: A network cloud.
270 | * `computer`: Something that looks kind of like an iMac.
271 |
272 | ### Creating Special Objects ################################################
273 |
274 | Although a tutorial on SVG is out of the scope of this document, it is
275 | possible to create your own custom objects for your own graphs! Fire up your
276 | favorite vector editor that is capable of producing SVG output (if you don't
277 | have one, [Inkscape][10] will work fine). Draw to your heart's content and
278 | save your drawing as an SVG image (you will need to use the path tool; any
279 | premade shapes aren't supported). Open the image and extract all the path
280 | tags; put them in a file called `object.path`. Place this file in the
281 | `objects` directory. Any time you reference `"a2s:type":"object"`, your new
282 | object will take the place of the box you created.
283 |
284 | There are a few caveats to this approach:
285 |
286 | * You *must* specify the width and height of the original canvas in every
287 | path, although there are no minimum or maximum values, nor is there any
288 | required aspect ratio.
289 | * The path (or any group it is in) of the original SVG file must not be
290 | transformed in any way. (Inkscape will do this if you change your
291 | document size after you start drawing, for instance.)
292 | * Extraneous tags (i.e. things that aren't ``) will probably
293 | break the object parser.
294 | * Objects are cached in `$TMPDIR/.a2s.objcache`. If multiple users are
295 | using ASCIIToSVG on the same machine, this probably isn't very safe.
296 | * Reference options are currently only applied to the first path;
297 | additional paths are drawn with the default options of the group. This
298 | means you probably want your first object to be an outline.
299 |
300 | If you're particularly happy with an object and want to share it, feel free
301 | to submit it!
302 |
303 | ## External Resources #######################################################
304 |
305 | There are some interesting sites that you might be interested in; these are
306 | mildly related to the goals of ASCIIToSVG:
307 |
308 | * [ditaa][1] (previously mentioned) is a Java utility with a very similar
309 | syntax that translates ASCII diagrams into PNG files.
310 | * [App::Asciio][4] (previously mentioned) allows you to programmatically
311 | draw ASCII diagrams.
312 | * [Asciiflow][6] is a web front-end to designing ASCII flowcharts.
313 |
314 | If you have something really cool that is related to ASCIIToSVG, and I have
315 | failed to list it here, do please let me know.
316 |
317 | ## References ###############################################################
318 |
319 | If there's nothing here, you're looking at this README post-markdown-ified.
320 |
321 | [1]: http://ditaa.sourceforge.net/ "ditaa - DIagrams Through ASCII Art"
322 | [2]: http://mtrack.wezfurlong.org/ "mtrack project management software"
323 | [3]: https://bitbucket.org/dhobsd/asciitosvg/wiki/Home "a2s wiki page"
324 | [4]: http://search.cpan.org/dist/App-Asciio/lib/App/Asciio.pm "App::Asciio"
325 | [5]: http://www.w3.org/TR/SVG/paths.html "SVG Paths"
326 | [6]: http://www.asciiflow.com/ "Asciiflow"
327 | [7]: http://bitbucket.org/wez/jlexphp "JLexPHP"
328 | [8]: https://bitbucket.org/wez/lemon-php "lemon-php"
329 | [9]: http://www.w3.org/TR/SVG/paths.html#PathDataBNF "SVG Path Grammar"
330 | [10]: http://inkscape.org/download "Download Inkscape"
331 |
--------------------------------------------------------------------------------
/svg-path.php:
--------------------------------------------------------------------------------
1 |
240 | ** A FILE* to which trace output should be written.
241 | ** If NULL, then tracing is turned off.
242 | ** A prefix string written at the beginning of every
243 | ** line of trace output. If NULL, then tracing is
244 | ** turned off.
245 | **
246 | **
247 | ** Outputs:
248 | ** None.
249 | */
250 | function A2S_SVGPathTrace(/* stream */ $TraceFILE, /* string */ $zTracePrompt){
251 | $this->yyTraceFILE = $TraceFILE;
252 | $this->yyTracePrompt = $zTracePrompt;
253 | if( $this->yyTraceFILE===null ) $this->yyTracePrompt = null;
254 | else if( $this->yyTracePrompt===null ) $this->yyTraceFILE = null;
255 | }
256 |
257 | /* For tracing shifts, the names of all terminals and nonterminals
258 | ** are required. The following table supplies these names */
259 | static $yyTokenName = array(
260 | '$', 'ANY', 'MCMD', 'ZCMD',
261 | 'LCMD', 'HCMD', 'VCMD', 'CCMD',
262 | 'SCMD', 'QCMD', 'TCMD', 'ACMD',
263 | 'POSNUM', 'FLAG', 'NEGNUM', 'error',
264 | 'svg_path', 'moveto_drawto_command_groups', 'moveto_drawto_command_group', 'moveto',
265 | 'drawto_commands', 'drawto_command', 'closepath', 'lineto',
266 | 'horizontal_lineto', 'vertical_lineto', 'curveto', 'smooth_curveto',
267 | 'quadratic_bezier_curveto', 'smooth_quadratic_bezier_curveto', 'elliptical_arc', 'moveto_argument_sequence',
268 | 'lineto_argument_sequence', 'coordinate_pair', 'horizontal_lineto_argument_sequence', 'coordinate',
269 | 'vertical_lineto_argument_sequence', 'curveto_argument_sequence', 'curveto_argument', 'smooth_curveto_argument_sequence',
270 | 'smooth_curveto_argument', 'quadratic_bezier_curveto_argument_sequence', 'quadratic_bezier_curveto_argument', 'smooth_quadratic_bezier_curveto_argument_sequence',
271 | 'elliptical_arc_argument_sequence', 'elliptical_arc_argument', 'number',
272 | );
273 |
274 | /* For tracing reduce actions, the names of all rules are required.
275 | */
276 | static $yyRuleName = array(
277 | /* 0 */ "svg_path ::= moveto_drawto_command_groups",
278 | /* 1 */ "moveto_drawto_command_groups ::= moveto_drawto_command_groups moveto_drawto_command_group",
279 | /* 2 */ "moveto_drawto_command_groups ::= moveto_drawto_command_group",
280 | /* 3 */ "moveto_drawto_command_group ::= moveto drawto_commands",
281 | /* 4 */ "drawto_commands ::= drawto_commands drawto_command",
282 | /* 5 */ "drawto_commands ::= drawto_command",
283 | /* 6 */ "drawto_command ::= closepath",
284 | /* 7 */ "drawto_command ::= lineto",
285 | /* 8 */ "drawto_command ::= horizontal_lineto",
286 | /* 9 */ "drawto_command ::= vertical_lineto",
287 | /* 10 */ "drawto_command ::= curveto",
288 | /* 11 */ "drawto_command ::= smooth_curveto",
289 | /* 12 */ "drawto_command ::= quadratic_bezier_curveto",
290 | /* 13 */ "drawto_command ::= smooth_quadratic_bezier_curveto",
291 | /* 14 */ "drawto_command ::= elliptical_arc",
292 | /* 15 */ "moveto ::= MCMD moveto_argument_sequence",
293 | /* 16 */ "moveto_argument_sequence ::= lineto_argument_sequence coordinate_pair",
294 | /* 17 */ "moveto_argument_sequence ::= coordinate_pair",
295 | /* 18 */ "closepath ::= ZCMD",
296 | /* 19 */ "lineto ::= LCMD lineto_argument_sequence",
297 | /* 20 */ "lineto_argument_sequence ::= lineto_argument_sequence coordinate_pair",
298 | /* 21 */ "lineto_argument_sequence ::= coordinate_pair",
299 | /* 22 */ "horizontal_lineto ::= HCMD horizontal_lineto_argument_sequence",
300 | /* 23 */ "horizontal_lineto_argument_sequence ::= horizontal_lineto_argument_sequence coordinate",
301 | /* 24 */ "horizontal_lineto_argument_sequence ::= coordinate",
302 | /* 25 */ "vertical_lineto ::= VCMD vertical_lineto_argument_sequence",
303 | /* 26 */ "vertical_lineto_argument_sequence ::= vertical_lineto_argument_sequence coordinate",
304 | /* 27 */ "vertical_lineto_argument_sequence ::= coordinate",
305 | /* 28 */ "curveto ::= CCMD curveto_argument_sequence",
306 | /* 29 */ "curveto_argument_sequence ::= curveto_argument_sequence curveto_argument",
307 | /* 30 */ "curveto_argument_sequence ::= curveto_argument",
308 | /* 31 */ "curveto_argument ::= coordinate_pair coordinate_pair coordinate_pair",
309 | /* 32 */ "smooth_curveto ::= SCMD smooth_curveto_argument_sequence",
310 | /* 33 */ "smooth_curveto_argument_sequence ::= smooth_curveto_argument_sequence smooth_curveto_argument",
311 | /* 34 */ "smooth_curveto_argument_sequence ::= smooth_curveto_argument",
312 | /* 35 */ "smooth_curveto_argument ::= coordinate_pair coordinate_pair",
313 | /* 36 */ "quadratic_bezier_curveto ::= QCMD quadratic_bezier_curveto_argument_sequence",
314 | /* 37 */ "quadratic_bezier_curveto_argument_sequence ::= quadratic_bezier_curveto_argument_sequence quadratic_bezier_curveto_argument",
315 | /* 38 */ "quadratic_bezier_curveto_argument_sequence ::= quadratic_bezier_curveto_argument",
316 | /* 39 */ "quadratic_bezier_curveto_argument ::= coordinate_pair coordinate_pair",
317 | /* 40 */ "smooth_quadratic_bezier_curveto ::= TCMD smooth_quadratic_bezier_curveto_argument_sequence",
318 | /* 41 */ "smooth_quadratic_bezier_curveto_argument_sequence ::= smooth_quadratic_bezier_curveto_argument_sequence coordinate_pair",
319 | /* 42 */ "smooth_quadratic_bezier_curveto_argument_sequence ::= coordinate_pair",
320 | /* 43 */ "elliptical_arc ::= ACMD elliptical_arc_argument_sequence",
321 | /* 44 */ "elliptical_arc_argument_sequence ::= elliptical_arc_argument_sequence elliptical_arc_argument",
322 | /* 45 */ "elliptical_arc_argument_sequence ::= elliptical_arc_argument",
323 | /* 46 */ "elliptical_arc_argument ::= POSNUM POSNUM number FLAG FLAG coordinate_pair",
324 | /* 47 */ "coordinate_pair ::= coordinate coordinate",
325 | /* 48 */ "coordinate ::= number",
326 | /* 49 */ "number ::= POSNUM",
327 | /* 50 */ "number ::= FLAG",
328 | /* 51 */ "number ::= NEGNUM",
329 | );
330 |
331 | /*
332 | ** This function returns the symbolic name associated with a token
333 | ** value.
334 | */
335 | function A2S_SVGPathTokenName(/* int */ $tokenType){
336 | if (isset(self::$yyTokenName[$tokenType]))
337 | return self::$yyTokenName[$tokenType];
338 | return "Unknown";
339 | }
340 |
341 | /* The following function deletes the value associated with a
342 | ** symbol. The symbol can be either a terminal or nonterminal.
343 | ** "yymajor" is the symbol code, and "yypminor" is a pointer to
344 | ** the value.
345 | */
346 | private function yy_destructor($yymajor, $yypminor){
347 | switch( $yymajor ){
348 | /* Here is inserted the actions which take place when a
349 | ** terminal or non-terminal is destroyed. This can happen
350 | ** when the symbol is popped from the stack during a
351 | ** reduce or during error processing or when a parser is
352 | ** being destroyed before it is finished parsing.
353 | **
354 | ** Note: during a reduce, the only symbols destroyed are those
355 | ** which appear on the RHS of the rule, but which are not used
356 | ** inside the C code.
357 | */
358 | default: break; /* If no destructor action specified: do nothing */
359 | }
360 | }
361 |
362 | /*
363 | ** Pop the parser's stack once.
364 | **
365 | ** If there is a destructor routine associated with the token which
366 | ** is popped from the stack, then call it.
367 | **
368 | ** Return the major token number for the symbol popped.
369 | */
370 | private function yy_pop_parser_stack() {
371 | if ($this->yyidx < 0) return 0;
372 | $yytos = $this->yystack[$this->yyidx];
373 | if( $this->yyTraceFILE ) {
374 | fprintf($this->yyTraceFILE,"%sPopping %s\n",
375 | $this->yyTracePrompt,
376 | self::$yyTokenName[$yytos->major]);
377 | }
378 | $this->yy_destructor( $yytos->major, $yytos->minor);
379 | unset($this->yystack[$this->yyidx]);
380 | $this->yyidx--;
381 | return $yytos->major;
382 | }
383 |
384 | /*
385 | ** Deallocate and destroy a parser. Destructors are all called for
386 | ** all stack elements before shutting the parser down.
387 | **
388 | ** Inputs:
389 | **
390 | ** - A pointer to the parser. This should be a pointer
391 | ** obtained from A2S_SVGPathAlloc.
392 | **
- A pointer to a function used to reclaim memory obtained
393 | ** from malloc.
394 | **
395 | */
396 | function __destruct()
397 | {
398 | while($this->yyidx >= 0)
399 | $this->yy_pop_parser_stack();
400 | }
401 |
402 | /*
403 | ** Find the appropriate action for a parser given the terminal
404 | ** look-ahead token iLookAhead.
405 | **
406 | ** If the look-ahead token is YYNOCODE, then check to see if the action is
407 | ** independent of the look-ahead. If it is, return the action, otherwise
408 | ** return YY_NO_ACTION.
409 | */
410 | private function yy_find_shift_action(
411 | $iLookAhead /* The look-ahead token */
412 | ){
413 | $i = 0;
414 | $stateno = $this->yystack[$this->yyidx]->stateno;
415 |
416 | if( $stateno>self::YY_SHIFT_MAX ||
417 | ($i = self::$yy_shift_ofst[$stateno])==self::YY_SHIFT_USE_DFLT ){
418 | return self::$yy_default[$stateno];
419 | }
420 | if( $iLookAhead==self::YYNOCODE ){
421 | return $this->YY_NO_ACTION;
422 | }
423 | $i += $iLookAhead;
424 | if( $i<0 || $i>=count(self::$yy_action) || self::$yy_lookahead[$i]!=$iLookAhead ){
425 | if( $iLookAhead>0 ){
426 | if (isset(self::$yyFallback[$iLookAhead]) &&
427 | ($iFallback = self::$yyFallback[$iLookAhead]) != 0) {
428 | if( $this->yyTraceFILE ){
429 | fprintf($this->yyTraceFILE, "%sFALLBACK %s => %s\n",
430 | $this->yyTracePrompt, self::$yyTokenName[$iLookAhead],
431 | self::$yyTokenName[$iFallback]);
432 | }
433 | return $this->yy_find_shift_action($iFallback);
434 | }
435 | {
436 | $j = $i - $iLookAhead + self::YYWILDCARD;
437 | if( $j>=0 && $jyyTraceFILE ){
439 | fprintf($this->yyTraceFILE, "%sWILDCARD %s => %s\n",
440 | $this->yyTracePrompt, self::$yyTokenName[$iLookAhead],
441 | self::$yyTokenName[self::YYWILDCARD]);
442 | }
443 | return self::$yy_action[$j];
444 | }
445 | }
446 | }
447 | return self::$yy_default[$stateno];
448 | }else{
449 | return self::$yy_action[$i];
450 | }
451 | }
452 |
453 | /*
454 | ** Find the appropriate action for a parser given the non-terminal
455 | ** look-ahead token iLookAhead.
456 | **
457 | ** If the look-ahead token is YYNOCODE, then check to see if the action is
458 | ** independent of the look-ahead. If it is, return the action, otherwise
459 | ** return YY_NO_ACTION.
460 | */
461 | private function yy_find_reduce_action(
462 | $stateno, /* Current state number */
463 | $iLookAhead /* The look-ahead token */
464 | ){
465 | $i = 0;
466 |
467 | if( $stateno>self::YY_REDUCE_MAX ||
468 | ($i = self::$yy_reduce_ofst[$stateno])==self::YY_REDUCE_USE_DFLT ){
469 | return self::$yy_default[$stateno];
470 | }
471 | if( $iLookAhead==self::YYNOCODE ){
472 | return $this->YY_NO_ACTION;
473 | }
474 | $i += $iLookAhead;
475 | if( $i<0 || $i>=count(self::$yy_action) || self::$yy_lookahead[$i]!=$iLookAhead ){
476 | return self::$yy_default[$stateno];
477 | }else{
478 | return self::$yy_action[$i];
479 | }
480 | }
481 |
482 | /*
483 | ** Perform a shift action.
484 | */
485 | private function yy_shift(
486 | $yyNewState, /* The new state to shift in */
487 | $yyMajor, /* The major token to shift in */
488 | $yypMinor /* Pointer ot the minor token to shift in */
489 | ){
490 | $this->yyidx++;
491 | if (isset($this->yystack[$this->yyidx])) {
492 | $yytos = $this->yystack[$this->yyidx];
493 | } else {
494 | $yytos = new A2S_SVGPathyyStackEntry;
495 | $this->yystack[$this->yyidx] = $yytos;
496 | }
497 | $yytos->stateno = $yyNewState;
498 | $yytos->major = $yyMajor;
499 | $yytos->minor = $yypMinor;
500 | if( $this->yyTraceFILE) {
501 | fprintf($this->yyTraceFILE,"%sShift %d\n",$this->yyTracePrompt,$yyNewState);
502 | fprintf($this->yyTraceFILE,"%sStack:",$this->yyTracePrompt);
503 | for ($i = 1; $i <= $this->yyidx; $i++) {
504 | $ent = $this->yystack[$i];
505 | fprintf($this->yyTraceFILE," %s",self::$yyTokenName[$ent->major]);
506 | }
507 | fprintf($this->yyTraceFILE,"\n");
508 | }
509 | }
510 |
511 | private function __overflow_dead_code() {
512 | /* if the stack can overflow (it can't in the PHP implementation)
513 | * Then the following code would be emitted */
514 | }
515 |
516 | /* The following table contains information about every rule that
517 | ** is used during the reduce.
518 | ** Rather than pollute memory with a large number of arrays,
519 | ** we store both data points in the same array, indexing by
520 | ** rule number * 2.
521 | static const struct {
522 | YYCODETYPE lhs; // Symbol on the left-hand side of the rule
523 | unsigned char nrhs; // Number of right-hand side symbols in the rule
524 | } yyRuleInfo[] = {
525 | */
526 | static $yyRuleInfo = array(
527 | 16, 1,
528 | 17, 2,
529 | 17, 1,
530 | 18, 2,
531 | 20, 2,
532 | 20, 1,
533 | 21, 1,
534 | 21, 1,
535 | 21, 1,
536 | 21, 1,
537 | 21, 1,
538 | 21, 1,
539 | 21, 1,
540 | 21, 1,
541 | 21, 1,
542 | 19, 2,
543 | 31, 2,
544 | 31, 1,
545 | 22, 1,
546 | 23, 2,
547 | 32, 2,
548 | 32, 1,
549 | 24, 2,
550 | 34, 2,
551 | 34, 1,
552 | 25, 2,
553 | 36, 2,
554 | 36, 1,
555 | 26, 2,
556 | 37, 2,
557 | 37, 1,
558 | 38, 3,
559 | 27, 2,
560 | 39, 2,
561 | 39, 1,
562 | 40, 2,
563 | 28, 2,
564 | 41, 2,
565 | 41, 1,
566 | 42, 2,
567 | 29, 2,
568 | 43, 2,
569 | 43, 1,
570 | 30, 2,
571 | 44, 2,
572 | 44, 1,
573 | 45, 6,
574 | 33, 2,
575 | 35, 1,
576 | 46, 1,
577 | 46, 1,
578 | 46, 1,
579 | );
580 |
581 | /*
582 | ** Perform a reduce action and the shift that must immediately
583 | ** follow the reduce.
584 | */
585 | private function yy_reduce(
586 | $yyruleno /* Number of the rule by which to reduce */
587 | ){
588 | $yygoto = 0; /* The next state */
589 | $yyact = 0; /* The next action */
590 | $yygotominor = null; /* The LHS of the rule reduced */
591 | $yymsp = null; /* The top of the parser's stack */
592 | $yysize = 0; /* Amount to pop the stack */
593 |
594 | $yymsp = $this->yystack[$this->yyidx];
595 | if( $this->yyTraceFILE && isset(self::$yyRuleName[$yyruleno])) {
596 | fprintf($this->yyTraceFILE, "%sReduce [%s].\n", $this->yyTracePrompt,
597 | self::$yyRuleName[$yyruleno]);
598 | }
599 |
600 | switch( $yyruleno ){
601 | /* Beginning here are the reduction cases. A typical example
602 | ** follows:
603 | ** case 0:
604 | ** #line
605 | ** { ... } // User supplied code
606 | ** #line
607 | ** break;
608 | */
609 | case 15:
610 | #line 26 "svg-path.y"
611 | {
612 | if (count($this->yystack[$this->yyidx + 0]->minor) == 2) {
613 | $this->commands[] = array_merge(array($this->yystack[$this->yyidx + -1]->minor), $this->yystack[$this->yyidx + 0]->minor);
614 | } else {
615 | if ($this->yystack[$this->yyidx + -1]->minor->value == 'm') {
616 | $arr = array ('value' => 'l');
617 | } else {
618 | $arr = array ('value' => 'L');
619 | }
620 | $c = array_splice($this->yystack[$this->yyidx + 0]->minor, 2);
621 | $this->commands[] = array_merge(array($this->yystack[$this->yyidx + -1]->minor), $this->yystack[$this->yyidx + 0]->minor);
622 | $this->commands[] = array_merge(array($arr), $c);
623 | }
624 | }
625 | #line 604 "svg-path.php"
626 | break;
627 | case 16:
628 | case 20:
629 | case 29:
630 | case 33:
631 | case 35:
632 | case 37:
633 | case 39:
634 | case 41:
635 | case 44:
636 | #line 42 "svg-path.y"
637 | { $yygotominor = array_merge($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
638 | #line 617 "svg-path.php"
639 | break;
640 | case 17:
641 | case 21:
642 | case 30:
643 | case 34:
644 | case 38:
645 | case 42:
646 | case 45:
647 | case 48:
648 | case 49:
649 | case 50:
650 | case 51:
651 | #line 43 "svg-path.y"
652 | { $yygotominor = $this->yystack[$this->yyidx + 0]->minor; }
653 | #line 632 "svg-path.php"
654 | break;
655 | case 18:
656 | #line 45 "svg-path.y"
657 | { $this->commands[] = array($this->yystack[$this->yyidx + 0]->minor); }
658 | #line 637 "svg-path.php"
659 | break;
660 | case 19:
661 | case 22:
662 | case 25:
663 | case 28:
664 | case 32:
665 | case 36:
666 | case 40:
667 | case 43:
668 | #line 48 "svg-path.y"
669 | { $this->commands[] = array_merge(array($this->yystack[$this->yyidx + -1]->minor), $this->yystack[$this->yyidx + 0]->minor); }
670 | #line 649 "svg-path.php"
671 | break;
672 | case 23:
673 | case 26:
674 | #line 59 "svg-path.y"
675 | { $yygotominor = array_merge($this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + 0]->minor)); }
676 | #line 655 "svg-path.php"
677 | break;
678 | case 24:
679 | case 27:
680 | #line 60 "svg-path.y"
681 | { $yygotominor = array($this->yystack[$this->yyidx + 0]->minor); }
682 | #line 661 "svg-path.php"
683 | break;
684 | case 31:
685 | #line 80 "svg-path.y"
686 | { $yygotominor = array_merge($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
687 | #line 666 "svg-path.php"
688 | break;
689 | case 46:
690 | #line 131 "svg-path.y"
691 | { $yygotominor = array_merge(array($this->yystack[$this->yyidx + -5]->minor, $this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor), $this->yystack[$this->yyidx + 0]->minor); }
692 | #line 671 "svg-path.php"
693 | break;
694 | case 47:
695 | #line 133 "svg-path.y"
696 | { $yygotominor = array($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
697 | #line 676 "svg-path.php"
698 | break;
699 | };
700 | $yygoto = self::$yyRuleInfo[2*$yyruleno];
701 | $yysize = self::$yyRuleInfo[(2*$yyruleno)+1];
702 |
703 | $state_for_reduce = $this->yystack[$this->yyidx - $yysize]->stateno;
704 |
705 | $this->yyidx -= $yysize;
706 | $yyact = $this->yy_find_reduce_action($state_for_reduce,$yygoto);
707 | if( $yyact < self::YYNSTATE ){
708 | $this->yy_shift($yyact, $yygoto, $yygotominor);
709 | }else if( $yyact == self::YYNSTATE + self::YYNRULE + 1 ){
710 | $this->yy_accept();
711 | }
712 | }
713 |
714 | /*
715 | ** The following code executes when the parse fails
716 | */
717 | private function yy_parse_failed(
718 | ){
719 | if( $this->yyTraceFILE ){
720 | fprintf($this->yyTraceFILE,"%sFail!\n",$this->yyTracePrompt);
721 | }
722 | while( $this->yyidx>=0 ) $this->yy_pop_parser_stack();
723 | /* Here code is inserted which will be executed whenever the
724 | ** parser fails */
725 | }
726 |
727 | /*
728 | ** The following code executes when a syntax error first occurs.
729 | */
730 | private function yy_syntax_error(
731 | $yymajor, /* The major type of the error token */
732 | $yyminor /* The minor type of the error token */
733 | ){
734 | }
735 |
736 | /*
737 | ** The following is executed when the parser accepts
738 | */
739 | private function yy_accept(
740 | ){
741 | if( $this->yyTraceFILE ){
742 | fprintf($this->yyTraceFILE,"%sAccept!\n",$this->yyTracePrompt);
743 | }
744 | while( $this->yyidx>=0 ) $this->yy_pop_parser_stack();
745 | /* Here code is inserted which will be executed whenever the
746 | ** parser accepts */
747 | }
748 |
749 | /* The main parser program.
750 | ** The first argument is a pointer to a structure obtained from
751 | ** "A2S_SVGPathAlloc" which describes the current state of the parser.
752 | ** The second argument is the major token number. The third is
753 | ** the minor token. The fourth optional argument is whatever the
754 | ** user wants (and specified in the grammar) and is available for
755 | ** use by the action routines.
756 | **
757 | ** Inputs:
758 | **
759 | ** - A pointer to the parser (an opaque structure.)
760 | **
- The major token number.
761 | **
- The minor token number.
762 | **
- An option argument of a grammar-specified type.
763 | **
764 | **
765 | ** Outputs:
766 | ** None.
767 | */
768 | function A2S_SVGPath(
769 | $yymajor, /* The major token code number */
770 | $yyminor = null /* The value for the token */
771 | ){
772 | $yyact = 0; /* The parser action. */
773 | $yyendofinput = 0; /* True if we are at the end of input */
774 | $yyerrorhit = 0; /* True if yymajor has invoked an error */
775 |
776 | /* (re)initialize the parser, if necessary */
777 | if( $this->yyidx<0 ){
778 | $this->yyidx = 0;
779 | $this->yyerrcnt = -1;
780 | $ent = new A2S_SVGPathyyStackEntry;
781 | $ent->stateno = 0;
782 | $ent->major = 0;
783 | $this->yystack = array( 0 => $ent );
784 |
785 | $this->YY_NO_ACTION = self::YYNSTATE + self::YYNRULE + 2;
786 | $this->YY_ACCEPT_ACTION = self::YYNSTATE + self::YYNRULE + 1;
787 | $this->YY_ERROR_ACTION = self::YYNSTATE + self::YYNRULE;
788 | }
789 | $yyendofinput = ($yymajor==0);
790 |
791 | if( $this->yyTraceFILE ){
792 | fprintf($this->yyTraceFILE,"%sInput %s\n",$this->yyTracePrompt,
793 | self::$yyTokenName[$yymajor]);
794 | }
795 |
796 | do{
797 | $yyact = $this->yy_find_shift_action($yymajor);
798 | if( $yyactyy_shift($yyact,$yymajor,$yyminor);
800 | $this->yyerrcnt--;
801 | if( $yyendofinput && $this->yyidx>=0 ){
802 | $yymajor = 0;
803 | }else{
804 | $yymajor = self::YYNOCODE;
805 | }
806 | }else if( $yyact < self::YYNSTATE + self::YYNRULE ){
807 | $this->yy_reduce($yyact-self::YYNSTATE);
808 | }else if( $yyact == $this->YY_ERROR_ACTION ){
809 | if( $this->yyTraceFILE ){
810 | fprintf($this->yyTraceFILE,"%sSyntax Error!\n",$this->yyTracePrompt);
811 | }
812 | if (self::YYERRORSYMBOL) {
813 | /* A syntax error has occurred.
814 | ** The response to an error depends upon whether or not the
815 | ** grammar defines an error token "ERROR".
816 | **
817 | ** This is what we do if the grammar does define ERROR:
818 | **
819 | ** * Call the %syntax_error function.
820 | **
821 | ** * Begin popping the stack until we enter a state where
822 | ** it is legal to shift the error symbol, then shift
823 | ** the error symbol.
824 | **
825 | ** * Set the error count to three.
826 | **
827 | ** * Begin accepting and shifting new tokens. No new error
828 | ** processing will occur until three tokens have been
829 | ** shifted successfully.
830 | **
831 | */
832 | if( $this->yyerrcnt<0 ){
833 | $this->yy_syntax_error($yymajor, $yyminor);
834 | }
835 | $yymx = $this->yystack[$this->yyidx]->major;
836 | if( $yymx==self::YYERRORSYMBOL || $yyerrorhit ){
837 | if( $this->yyTraceFILE ){
838 | fprintf($this->yyTraceFILE,"%sDiscard input token %s\n",
839 | $this->yyTracePrompt,self::$yyTokenName[$yymajor]);
840 | }
841 | $this->yy_destructor($yymajor,$yyminor);
842 | $yymajor = self::YYNOCODE;
843 | }else{
844 | while(
845 | $this->yyidx >= 0 &&
846 | $yymx != self::YYERRORSYMBOL &&
847 | ($yyact = $this->yy_find_reduce_action(
848 | $this->yystack[$this->yyidx]->stateno,
849 | self::YYERRORSYMBOL)) >= self::YYNSTATE
850 | ){
851 | $this->yy_pop_parser_stack();
852 | }
853 | if( $this->yyidx < 0 || $yymajor==0 ){
854 | $this->yy_destructor($yymajor,$yyminor);
855 | $this->yy_parse_failed();
856 | $yymajor = self::YYNOCODE;
857 | }else if( $yymx!=self::YYERRORSYMBOL ){
858 | $this->yy_shift($yyact,self::YYERRORSYMBOL,0);
859 | }
860 | }
861 | $this->yyerrcnt = 3;
862 | $yyerrorhit = 1;
863 | } else { /* YYERRORSYMBOL is not defined */
864 | /* This is what we do if the grammar does not define ERROR:
865 | **
866 | ** * Report an error message, and throw away the input token.
867 | **
868 | ** * If the input token is $, then fail the parse.
869 | **
870 | ** As before, subsequent error messages are suppressed until
871 | ** three input tokens have been successfully shifted.
872 | */
873 | if( $this->yyerrcnt<=0 ){
874 | $this->yy_syntax_error($yymajor, $yyminor);
875 | }
876 | $this->yyerrcnt = 3;
877 | $this->yy_destructor($yymajor,$yyminor);
878 | if( $yyendofinput ){
879 | $this->yy_parse_failed();
880 | }
881 | $yymajor = self::YYNOCODE;
882 | }
883 | }else{
884 | $this->yy_accept();
885 | $yymajor = self::YYNOCODE;
886 | }
887 | }while( $yymajor!=self::YYNOCODE && $this->yyidx>=0 );
888 | }
889 |
890 | }
891 |
--------------------------------------------------------------------------------
/a2s52.php:
--------------------------------------------------------------------------------
1 | SVG art generator.
4 | * Copyright © 2012 Devon H. O'Dell
5 | * All rights reserved.
6 | *
7 | * Redistribution and use in source and binary forms, with or without
8 | * modification, are permitted provided that the following conditions
9 | * are met:
10 | *
11 | * o Redistributions of source code must retain the above copyright notice,
12 | * this list of conditions and the following disclaimer.
13 | * o Redistributions in binary form must reproduce the above copyright notice,
14 | * this list of conditions and the following disclaimer in the documentation
15 | * and/or other materials provided with the distribution.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 | * POSSIBILITY OF SUCH DAMAGE.
28 | *
29 | */
30 |
31 | #namespace org\dh0\a2s;
32 |
33 | include 'svg-path.lex.php';
34 | include 'colors.php';
35 |
36 | /*
37 | * A2S_Scale is a singleton class that is instantiated to apply scale
38 | * transformations on the text -> canvas grid geometry. We could probably use
39 | * SVG's native scaling for this, but I'm not sure how yet.
40 | */
41 | class A2S_Scale {
42 | private static $instance = null;
43 |
44 | public $xScale;
45 | public $yScale;
46 |
47 | private function __construct() {}
48 | private function __clone() {}
49 |
50 | public static function getInstance() {
51 | if (self::$instance == null) {
52 | self::$instance = new A2S_Scale();
53 | }
54 |
55 | return self::$instance;
56 | }
57 |
58 | public function setScale($x, $y) {
59 | $o = self::getInstance();
60 | $o->xScale = $x;
61 | $o->yScale = $y;
62 | }
63 | }
64 |
65 | /*
66 | * A2S_CustomObjects allows users to create their own custom SVG paths and use
67 | * them as box types with a2s:type references.
68 | *
69 | * Paths must have width and height set, and must not span multiple lines.
70 | * Multiple paths can be specified, one path per line. All objects must
71 | * reside in the same directory.
72 | *
73 | * File operations are horribly slow, so we make a best effort to avoid
74 | * as many as possible:
75 | *
76 | * * If the directory mtime hasn't changed, we attempt to load our
77 | * objects from a cache file.
78 | *
79 | * * If this file doesn't exist, can't be read, or the mtime has
80 | * changed, we scan the directory and update files that have changed
81 | * based on their mtime.
82 | *
83 | * * We attempt to save our cache in a temporary directory. It's volatile
84 | * but also requires no configuration.
85 | *
86 | * We could do a bit better by utilizing APC's shared memory storage, which
87 | * would help greatly when running on a server.
88 | *
89 | * Note that the path parser isn't foolproof, mostly because PHP isn't the
90 | * greatest language ever for implementing a parser.
91 | */
92 | class A2S_CustomObjects {
93 | public static $objects = array();
94 |
95 | /*
96 | * Closures / callable function names / whatever for integrating non-default
97 | * loading and storage functionality.
98 | */
99 | public static $loadCacheFn = null;
100 | public static $storCacheFn = null;
101 | public static $loadObjsFn = null;
102 |
103 |
104 | public static function loadObjects() {
105 | $cacheFile = sys_get_temp_dir() . "/.a2s.objcache";
106 | $dir = './objects';
107 |
108 | if (is_callable(self::$loadCacheFn)) {
109 | /*
110 | * Should return exactly what was given to the $storCacheFn when it was
111 | * last called, or null if nothing can be loaded.
112 | */
113 | $fn = self::$loadCacheFn;
114 | self::$objects = $fn();
115 | return;
116 | } else {
117 | if (is_readable($cacheFile) && is_readable($dir)) {
118 | $cacheTime = filemtime($cacheFile);
119 |
120 | if (filemtime($dir) <= filemtime($cacheFile)) {
121 | self::$objects = unserialize(file_get_contents($cacheFile));
122 | return;
123 | }
124 | } else {
125 | return;
126 | }
127 | }
128 |
129 | if (is_callable(self::$loadObjsFn)) {
130 | /*
131 | * Returns an array of arrays of path information. The innermost arrays
132 | * (containing the path information) contain the path name, the width of
133 | * the bounding box, the height of the bounding box, and the path
134 | * command. This interface does *not* want the path's XML tag. An array
135 | * returned from here containing two objects that each have 1 line would
136 | * look like:
137 | *
138 | * array (
139 | * array (
140 | * name => 'pathA',
141 | * paths => array (
142 | * array ('width' => 10, 'height' => 10, 'path' => 'M 0 0 L 10 10'),
143 | * array ('width' => 10, 'height' => 10, 'path' => 'M 0 10 L 10 0'),
144 | * ),
145 | * ),
146 | * array (
147 | * name => 'pathB',
148 | * paths => array (
149 | * array ('width' => 10, 'height' => 10, 'path' => 'M 0 5 L 5 10'),
150 | * array ('width' => 10, 'height' => 10, 'path' => 'M 5 10 L 10 5'),
151 | * ),
152 | * ),
153 | * );
154 | */
155 | $fn = self::$loadObjsFn;
156 | $objs = $fn();
157 |
158 | $i = 0;
159 | foreach ($objs as $obj) {
160 | foreach ($obj['paths'] as $path) {
161 | self::$objects[$obj['name']][$i]['width'] = $path['width'];
162 | self::$objects[$obj['name']][$i]['height'] = $path['height'];
163 | self::$objects[$obj['name']][$i++]['path'] =
164 | self::parsePath($path['path']);
165 | }
166 | }
167 | } else {
168 | $ents = scandir($dir);
169 | foreach ($ents as $ent) {
170 | $file = "{$dir}/{$ent}";
171 | $base = substr($ent, 0, -5);
172 | if (substr($ent, -5) == '.path' && is_readable($file)) {
173 | if (isset(self::$objects[$base]) &&
174 | filemtime($file) <= self::$cacheTime) {
175 | continue;
176 | }
177 |
178 | $lines = file($file);
179 |
180 | $i = 0;
181 | foreach ($lines as $line) {
182 | preg_match('/width="(\d+)/', $line, $m);
183 | $width = $m[1];
184 | preg_match('/height="(\d+)/', $line, $m);
185 | $height = $m[1];
186 | preg_match('/d="([^"]+)"/', $line, $m);
187 | $path = $m[1];
188 |
189 | self::$objects[$base][$i]['width'] = $width;
190 | self::$objects[$base][$i]['height'] = $height;
191 | self::$objects[$base][$i++]['path'] = self::parsePath($path);
192 | }
193 | }
194 | }
195 | }
196 |
197 | if (is_callable(self::$storCacheFn)) {
198 | $fn = self::$storCacheFn;
199 | $fn(self::$objects);
200 | } else {
201 | file_put_contents($cacheFile, serialize(self::$objects));
202 | }
203 | }
204 |
205 | private static function parsePath($path) {
206 | $stream = fopen("data://text/plain,{$path}", 'r');
207 |
208 | $P = new A2S_SVGPathParser();
209 | $S = new A2S_Yylex($stream);
210 |
211 | while ($t = $S->nextToken()) {
212 | $P->A2S_SVGPath($t->type, $t);
213 | }
214 | /* Force shift/reduce of last token. */
215 | $P->A2S_SVGPath(0);
216 |
217 | fclose($stream);
218 |
219 | $cmdArr = array();
220 | $i = 0;
221 | foreach ($P->commands as $cmd) {
222 | foreach ($cmd as $arg) {
223 | $arg = (array)$arg;
224 | $cmdArr[$i][] = $arg['value'];
225 | }
226 | $i++;
227 | }
228 |
229 | return $cmdArr;
230 | }
231 | }
232 |
233 | /*
234 | * All lines and polygons are represented as a series of point coordinates
235 | * along a path. Points can have different properties; markers appear on
236 | * edges of lines and control points denote that a bezier curve should be
237 | * calculated for the corner represented by this point.
238 | */
239 | class A2S_Point {
240 | public $gridX;
241 | public $gridY;
242 |
243 | public $x;
244 | public $y;
245 |
246 | public $flags;
247 |
248 | const POINT = 0x1;
249 | const CONTROL = 0x2;
250 | const SMARKER = 0x4;
251 | const IMARKER = 0x8;
252 | const TICK = 0x10;
253 | const DOT = 0x20;
254 |
255 | public function __construct($x, $y) {
256 | $this->flags = 0;
257 |
258 | $s = A2S_Scale::getInstance();
259 | $this->x = ($x * $s->xScale) + ($s->xScale / 2);
260 | $this->y = ($y * $s->yScale) + ($s->yScale / 2);
261 |
262 | $this->gridX = $x;
263 | $this->gridY = $y;
264 | }
265 | }
266 |
267 | /*
268 | * Groups objects together and sets common properties for the objects in the
269 | * group.
270 | */
271 | class A2S_SVGGroup {
272 | private $groups;
273 | private $curGroup;
274 | private $groupStack;
275 | private $options;
276 |
277 | public function __construct() {
278 | $this->groups = array();
279 | $this->groupStack = array();
280 | $this->options = array();
281 | }
282 |
283 | public function getGroup($groupName) {
284 | return $this->groups[$groupName];
285 | }
286 |
287 | public function pushGroup($groupName) {
288 | if (!isset($this->groups[$groupName])) {
289 | $this->groups[$groupName] = array();
290 | $this->options[$groupName] = array();
291 | }
292 |
293 | $this->groupStack[] = $groupName;
294 | $this->curGroup = $groupName;
295 | }
296 |
297 | public function popGroup() {
298 | /*
299 | * Remove the last group and fetch the current one. array_pop will return
300 | * NULL for an empty array, so this is safe to do when only one element
301 | * is left.
302 | */
303 | array_pop($this->groupStack);
304 | $this->curGroup = array_pop($this->groupStack);
305 | }
306 |
307 | public function addObject($o) {
308 | $this->groups[$this->curGroup][] = $o;
309 | }
310 |
311 | public function setOption($opt, $val) {
312 | $this->options[$this->curGroup][$opt] = $val;
313 | }
314 |
315 | public function render() {
316 | $out = '';
317 |
318 | foreach($this->groups as $groupName => $objects) {
319 | $out .= "options[$groupName] as $opt => $val) {
321 | if (strpos($opt, 'a2s:', 0) === 0) {
322 | continue;
323 | }
324 | $out .= "$opt=\"$val\" ";
325 | }
326 | $out .= ">\n";
327 |
328 | foreach($objects as $obj) {
329 | $out .= $obj->render();
330 | }
331 |
332 | $out .= "\n";
333 | }
334 |
335 | return $out;
336 | }
337 | }
338 |
339 | /*
340 | * The Path class represents lines and polygons.
341 | */
342 | class A2S_SVGPath {
343 | private $options;
344 | private $points;
345 | private $ticks;
346 | private $flags;
347 | private $text;
348 | private $name;
349 |
350 | private static $id = 0;
351 |
352 | const CLOSED = 0x1;
353 |
354 | public function __construct() {
355 | $this->options = array();
356 | $this->points = array();
357 | $this->text = array();
358 | $this->ticks = array();
359 | $this->flags = 0;
360 | $this->name = self::$id++;
361 | }
362 |
363 | /*
364 | * Making sure that we always started at the top left coordinate
365 | * makes so many things so much easier. First, find the lowest Y
366 | * position. Then, of all matching Y positions, find the lowest X
367 | * position. This is the top left.
368 | *
369 | * As far as the points are considered, they're definitely on the
370 | * top somewhere, but not necessarily the most left. This could
371 | * happen if there was a corner connector in the top edge (perhaps
372 | * for a line to connect to). Since we couldn't turn right there,
373 | * we have to try now.
374 | *
375 | * This should only be called when we close a polygon.
376 | */
377 | public function orderPoints() {
378 | $pPoints = count($this->points);
379 |
380 | $minY = $this->points[0]->y;
381 | $minX = $this->points[0]->x;
382 | $minIdx = 0;
383 | for ($i = 1; $i < $pPoints; $i++) {
384 | if ($this->points[$i]->y <= $minY) {
385 | $minY = $this->points[$i]->y;
386 |
387 | if ($this->points[$i]->x < $minX) {
388 | $minX = $this->points[$i]->x;
389 | $minIdx = $i;
390 | }
391 | }
392 | }
393 |
394 | /*
395 | * If our top left isn't at the 0th index, it is at the end. If
396 | * there are bits after it, we need to cut those and put them at
397 | * the front.
398 | */
399 | if ($minIdx != 0) {
400 | $startPoints = array_splice($this->points, $minIdx);
401 | $this->points = array_merge($startPoints, $this->points);
402 | }
403 | }
404 |
405 | /*
406 | * Useful for recursive walkers when speculatively trying a direction.
407 | */
408 | public function popPoint() {
409 | array_pop($this->points);
410 | }
411 |
412 | public function addPoint($x, $y, $flags = A2S_Point::POINT) {
413 | $p = new A2S_Point($x, $y);
414 |
415 | /*
416 | * If we attempt to add our original point back to the path, the polygon
417 | * must be closed.
418 | */
419 | if (count($this->points) > 0) {
420 | if ($this->points[0]->x == $p->x && $this->points[0]->y == $p->y) {
421 | $this->flags |= self::CLOSED;
422 | return true;
423 | }
424 |
425 | /*
426 | * For the purposes of this library, paths should never intersect each
427 | * other. Even in the case of closing the polygon, we do not store the
428 | * final coordinate twice.
429 | */
430 | foreach ($this->points as $point) {
431 | if ($point->x == $p->x && $point->y == $p->y) {
432 | return true;
433 | }
434 | }
435 | }
436 |
437 | $p->flags |= $flags;
438 | $this->points[] = $p;
439 |
440 | return false;
441 | }
442 |
443 | /*
444 | * It's useful to be able to know the points in a shape.
445 | */
446 | public function getPoints() {
447 | return $this->points;
448 | }
449 |
450 | /*
451 | * Add a marker to a line. The third argument specifies which marker to use,
452 | * and this depends on the orientation of the line. Due to the way the line
453 | * parser works, we may have to use an inverted representation.
454 | */
455 | public function addMarker($x, $y, $t) {
456 | $p = new A2S_Point($x, $y);
457 | $p->flags |= $t;
458 | $this->points[] = $p;
459 | }
460 |
461 | public function addTick($x, $y, $t) {
462 | $p = new A2S_Point($x, $y);
463 | $p->flags |= $t;
464 | $this->ticks[] = $p;
465 | }
466 |
467 | /*
468 | * Is this path closed?
469 | */
470 | public function isClosed() {
471 | return ($this->flags & self::CLOSED);
472 | }
473 |
474 | public function addText($t) {
475 | $this->text[] = $t;
476 | }
477 |
478 | public function getText() {
479 | return $this->text;
480 | }
481 |
482 | public function setID($id) {
483 | $this->name = str_replace(' ', '_', str_replace('"', '_', $id));
484 | }
485 |
486 | public function getID() {
487 | return $this->name;
488 | }
489 |
490 | /*
491 | * Set options as a JSON string. Specified as a merge operation so that it
492 | * can be called after an individual setOption call.
493 | */
494 | public function setOptions($opt) {
495 | $this->options = array_merge($this->options, $opt);
496 | }
497 |
498 | public function setOption($opt, $val) {
499 | $this->options[$opt] = $val;
500 | }
501 |
502 | public function getOption($opt) {
503 | if (isset($this->options[$opt])) {
504 | return $this->options[$opt];
505 | }
506 |
507 | return null;
508 | }
509 |
510 | /*
511 | * Does the given point exist within this polygon? Since we can
512 | * theoretically have some complex concave and convex polygon edges in the
513 | * same shape, we need to do a full point-in-polygon test. This algorithm
514 | * seems like the standard one. See: http://alienryderflex.com/polygon/
515 | */
516 | public function hasPoint($x, $y) {
517 | if ($this->isClosed() == false) {
518 | return false;
519 | }
520 |
521 | $oddNodes = false;
522 |
523 | $bound = count($this->points);
524 | for ($i = 0, $j = count($this->points) - 1; $i < $bound; $i++) {
525 | if (($this->points[$i]->gridY < $y && $this->points[$j]->gridY >= $y ||
526 | $this->points[$j]->gridY < $y && $this->points[$i]->gridY >= $y) &&
527 | ($this->points[$i]->gridX <= $x || $this->points[$j]->gridX <= $x)) {
528 | if ($this->points[$i]->gridX + ($y - $this->points[$i]->gridY) /
529 | ($this->points[$j]->gridY - $this->points[$i]->gridY) *
530 | ($this->points[$j]->gridX - $this->points[$i]->gridX) < $x) {
531 | $oddNodes = !$oddNodes;
532 | }
533 | }
534 |
535 | $j = $i;
536 | }
537 |
538 | return $oddNodes;
539 | }
540 |
541 | /*
542 | * Apply a matrix transformation to the coordinates ($x, $y). The
543 | * multiplication is implemented on the matrices:
544 | *
545 | * | a b c | | x |
546 | * | d e f | * | y |
547 | * | 0 0 1 | | 1 |
548 | *
549 | * Additional information on the transformations and what each R,C in the
550 | * transformation matrix represents, see:
551 | *
552 | * http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
553 | */
554 | private function matrixTransform($matrix, $x, $y) {
555 | $xyMat = array(array($x), array($y), array(1));
556 | $newXY = array(array());
557 |
558 | for ($i = 0; $i < 3; $i++) {
559 | for ($j = 0; $j < 1; $j++) {
560 | $sum = 0;
561 |
562 | for ($k = 0; $k < 3; $k++) {
563 | $sum += $matrix[$i][$k] * $xyMat[$k][$j];
564 | }
565 |
566 | $newXY[$i][$j] = $sum;
567 | }
568 | }
569 |
570 | /* Return the coordinates as a vector */
571 | return array($newXY[0][0], $newXY[1][0], $newXY[2][0]);
572 | }
573 |
574 | /*
575 | * Translate the X and Y coordinates. tX and tY specify the distance to
576 | * transform.
577 | */
578 | private function translateTransform($tX, $tY, $x, $y) {
579 | $matrix = array(array(1, 0, $tX), array(0, 1, $tY), array(0, 0, 1));
580 | return $this->matrixTransform($matrix, $x, $y);
581 | }
582 |
583 | /*
584 | * A2S_Scale transformations are implemented by applying the scale to the X and
585 | * Y coordinates. One unit in the new coordinate system equals $s[XY] units
586 | * in the old system. Thus, if you want to double the size of an object on
587 | * both axes, you sould call scaleTransform(0.5, 0.5, $x, $y)
588 | */
589 | private function scaleTransform($sX, $sY, $x, $y) {
590 | $matrix = array(array($sX, 0, 0), array(0, $sY, 0), array(0, 0, 1));
591 | return $this->matrixTransform($matrix, $x, $y);
592 | }
593 |
594 | /*
595 | * Rotate the coordinates around the center point cX and cY. If these
596 | * are not specified, the coordinate is rotated around 0,0. The angle
597 | * is specified in degrees.
598 | */
599 | private function rotateTransform($angle, $x, $y, $cX = 0, $cY = 0) {
600 | $angle = $angle * (pi() / 180);
601 | if ($cX != 0 || $cY != 0) {
602 | list ($x, $y) = $this->translateTransform(-$cX, -$cY, $x, $y);
603 | }
604 |
605 | $matrix = array(array(cos($angle), -sin($angle), 0),
606 | array(sin($angle), cos($angle), 0),
607 | array(0, 0, 1));
608 | $ret = $this->matrixTransform($matrix, $x, $y);
609 |
610 | if ($cX != 0 || $cY != 0) {
611 | list ($x, $y) = $this->translateTransform($cX, $cY, $ret[0], $ret[1]);
612 | $ret[0] = $x;
613 | $ret[1] = $y;
614 | }
615 |
616 | return $ret;
617 | }
618 |
619 | /*
620 | * Skews along the X axis at specified angle. The angle is specified in
621 | * degrees.
622 | */
623 | private function skewXTransform($angle, $x, $y) {
624 | $angle = $angle * (pi() / 180);
625 | $matrix = array(array(1, tan($angle), 0), array(0, 1, 0), array(0, 0, 1));
626 | return $this->matrixTransform($matrix, $x, $y);
627 | }
628 |
629 | /*
630 | * Skews along the Y axis at specified angle. The angle is specified in
631 | * degrees.
632 | */
633 | private function skewYTransform($angle, $x, $y) {
634 | $angle = $angle * (pi() / 180);
635 | $matrix = array(array(1, 0, 0), array(tan($angle), 1, 0), array(0, 0, 1));
636 | return $this->matrixTransform($matrix, $x, $y);
637 | }
638 |
639 | /*
640 | * Apply a transformation to a point $p.
641 | */
642 | private function applyTransformToPoint($txf, $p, $args) {
643 | switch ($txf) {
644 | case 'translate':
645 | return $this->translateTransform($args[0], $args[1], $p->x, $p->y);
646 |
647 | case 'scale':
648 | return $this->scaleTransform($args[0], $args[1], $p->x, $p->y);
649 |
650 | case 'rotate':
651 | if (count($args) > 1) {
652 | return $this->rotateTransform($args[0], $p->x, $p->y, $args[1], $args[2]);
653 | } else {
654 | return $this->rotateTransform($args[0], $p->x, $p->y);
655 | }
656 |
657 | case 'skewX':
658 | return $this->skewXTransform($args[0], $p->x, $p->y);
659 |
660 | case 'skewY':
661 | return $this->skewYTransform($args[0], $p->x, $p->y);
662 | }
663 | }
664 |
665 | /*
666 | * Apply the transformation function $txf to all coordinates on path $p
667 | * providing $args as arguments to the transformation function.
668 | */
669 | private function applyTransformToPath($txf, &$p, $args) {
670 | $pathCmds = count($p['path']);
671 | $curPoint = new A2S_Point(0, 0);
672 | $prevType = null;
673 | $curType = null;
674 |
675 | for ($i = 0; $i < $pathCmds; $i++) {
676 | $cmd = &$p['path'][$i];
677 |
678 | $prevType = $curType;
679 | $curType = $cmd[0];
680 |
681 | switch ($curType) {
682 | case 'z':
683 | case 'Z':
684 | /* Can't transform this */
685 | break;
686 |
687 | case 'm':
688 | if ($prevType != null) {
689 | $curPoint->x += $cmd[1];
690 | $curPoint->y += $cmd[2];
691 |
692 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
693 | $curPoint->x = $x;
694 | $curPoint->y = $y;
695 |
696 | $cmd[1] = $x;
697 | $cmd[2] = $y;
698 | } else {
699 | $curPoint->x = $cmd[1];
700 | $curPoint->y = $cmd[2];
701 |
702 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
703 | $curPoint->x = $x;
704 | $curPoint->y = $y;
705 |
706 | $cmd[1] = $x;
707 | $cmd[2] = $y;
708 | $curType = 'l';
709 | }
710 |
711 | break;
712 |
713 | case 'M':
714 | $curPoint->x = $cmd[1];
715 | $curPoint->y = $cmd[2];
716 |
717 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
718 | $curPoint->x = $x;
719 | $curPoint->y = $y;
720 |
721 | $cmd[1] = $x;
722 | $cmd[2] = $y;
723 |
724 | if ($prevType == null) {
725 | $curType = 'L';
726 | }
727 | break;
728 |
729 | case 'l':
730 | $curPoint->x += $cmd[1];
731 | $curPoint->y += $cmd[2];
732 |
733 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
734 | $curPoint->x = $x;
735 | $curPoint->y = $y;
736 |
737 | $cmd[1] = $x;
738 | $cmd[2] = $y;
739 |
740 | break;
741 |
742 | case 'L':
743 | $curPoint->x = $cmd[1];
744 | $curPoint->y = $cmd[2];
745 |
746 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
747 | $curPoint->x = $x;
748 | $curPoint->y = $y;
749 |
750 | $cmd[1] = $x;
751 | $cmd[2] = $y;
752 |
753 | break;
754 |
755 | case 'v':
756 | $curPoint->y += $cmd[1];
757 | $curPoint->x += 0;
758 |
759 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
760 | $curPoint->x = $x;
761 | $curPoint->y = $y;
762 |
763 | $cmd[1] = $y;
764 |
765 | break;
766 |
767 | case 'V':
768 | $curPoint->y = $cmd[1];
769 |
770 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
771 | $curPoint->x = $x;
772 | $curPoint->y = $y;
773 |
774 | $cmd[1] = $y;
775 |
776 | break;
777 |
778 | case 'h':
779 | $curPoint->x += $cmd[1];
780 |
781 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
782 | $curPoint->x = $x;
783 | $curPoint->y = $y;
784 |
785 | $cmd[1] = $x;
786 |
787 | break;
788 |
789 | case 'H':
790 | $curPoint->x = $cmd[1];
791 |
792 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
793 | $curPoint->x = $x;
794 | $curPoint->y = $y;
795 |
796 | $cmd[1] = $x;
797 |
798 | break;
799 |
800 | case 'c':
801 | $tP = new A2S_Point(0, 0);
802 | $tP->x = $curPoint->x + $cmd[1]; $tP->y = $curPoint->y + $cmd[2];
803 | list ($x, $y) = $this->applyTransformToPoint($txf, $tP, $args);
804 | $cmd[1] = $x;
805 | $cmd[2] = $y;
806 |
807 | $tP->x = $curPoint->x + $cmd[3]; $tP->y = $curPoint->y + $cmd[4];
808 | list ($x, $y) = $this->applyTransformToPoint($txf, $tP, $args);
809 | $cmd[3] = $x;
810 | $cmd[4] = $y;
811 |
812 | $curPoint->x += $cmd[5];
813 | $curPoint->y += $cmd[6];
814 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
815 |
816 | $curPoint->x = $x;
817 | $curPoint->y = $y;
818 | $cmd[5] = $x;
819 | $cmd[6] = $y;
820 |
821 | break;
822 | case 'C':
823 | $curPoint->x = $cmd[1];
824 | $curPoint->y = $cmd[2];
825 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
826 | $cmd[1] = $x;
827 | $cmd[2] = $y;
828 |
829 | $curPoint->x = $cmd[3];
830 | $curPoint->y = $cmd[4];
831 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
832 | $cmd[3] = $x;
833 | $cmd[4] = $y;
834 |
835 | $curPoint->x = $cmd[5];
836 | $curPoint->y = $cmd[6];
837 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
838 |
839 | $curPoint->x = $x;
840 | $curPoint->y = $y;
841 | $cmd[5] = $x;
842 | $cmd[6] = $y;
843 |
844 | break;
845 |
846 | case 's':
847 | case 'S':
848 |
849 | case 'q':
850 | case 'Q':
851 |
852 | case 't':
853 | case 'T':
854 |
855 | case 'a':
856 | break;
857 |
858 | case 'A':
859 | /*
860 | * This radius is relative to the start and end points, so it makes
861 | * sense to scale, rotate, or skew it, but not translate it.
862 | */
863 | if ($txf != 'translate') {
864 | $curPoint->x = $cmd[1];
865 | $curPoint->y = $cmd[2];
866 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
867 | $cmd[1] = $x;
868 | $cmd[2] = $y;
869 | }
870 |
871 | $curPoint->x = $cmd[6];
872 | $curPoint->y = $cmd[7];
873 | list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
874 | $curPoint->x = $x;
875 | $curPoint->y = $y;
876 | $cmd[6] = $x;
877 | $cmd[7] = $y;
878 |
879 | break;
880 | }
881 | }
882 | }
883 |
884 | public function render() {
885 | $startPoint = array_shift($this->points);
886 | $endPoint = $this->points[count($this->points) - 1];
887 |
888 | $out = "name}\">\n";
889 |
890 | /*
891 | * If someone has specified one of our special object types, we are going
892 | * to want to completely override any of the pathing that we would have
893 | * done otherwise, but we defer until here to do anything about it because
894 | * we need information about the object we're replacing.
895 | */
896 | if (isset($this->options['a2s:type']) &&
897 | isset(A2S_CustomObjects::$objects[$this->options['a2s:type']])) {
898 | $object = A2S_CustomObjects::$objects[$this->options['a2s:type']];
899 |
900 | /* Again, if no fill was specified, specify one. */
901 | if (!isset($this->options['fill'])) {
902 | $this->options['fill'] = '#fff';
903 | }
904 |
905 | /*
906 | * We don't care so much about the area, but we do care about the width
907 | * and height of the object. All of our "custom" objects are implemented
908 | * in 100x100 space, which makes the transformation marginally easier.
909 | */
910 | $minX = $startPoint->x; $maxX = $minX;
911 | $minY = $startPoint->y; $maxY = $minY;
912 | foreach ($this->points as $p) {
913 | if ($p->x < $minX) {
914 | $minX = $p->x;
915 | } elseif ($p->x > $maxX) {
916 | $maxX = $p->x;
917 | }
918 | if ($p->y < $minY) {
919 | $minY = $p->y;
920 | } elseif ($p->y > $maxY) {
921 | $maxY = $p->y;
922 | }
923 | }
924 |
925 | $objW = $maxX - $minX;
926 | $objH = $maxY - $minY;
927 |
928 | $i = 0;
929 | foreach ($object as $o) {
930 | $id = self::$id++;
931 | $out .= "\tname}\" d=\"";
932 |
933 | $oW = $o['width'];
934 | $oH = $o['height'];
935 |
936 | $this->applyTransformToPath('scale', $o, array($objW/$oW, $objH/$oH));
937 | $this->applyTransformToPath('translate', $o, array($minX, $minY));
938 |
939 | foreach ($o['path'] as $cmd) {
940 | $out .= join(' ', $cmd) . ' ';
941 | }
942 | $out .= '" ';
943 |
944 | /* Don't add options to sub-paths */
945 | if ($i++ < 1) {
946 | foreach ($this->options as $opt => $val) {
947 | if (strpos($opt, 'a2s:', 0) === 0) {
948 | continue;
949 | }
950 | $out .= "$opt=\"$val\" ";
951 | }
952 | }
953 |
954 | $out .= " />\n";
955 | }
956 |
957 | if (count($this->text) > 0) {
958 | foreach ($this->text as $text) {
959 | $out .= "\t" . $text->render() . "\n";
960 | }
961 | }
962 | $out .= "\n";
963 |
964 | /* Bazinga. */
965 | return $out;
966 | }
967 |
968 | /*
969 | * Nothing fancy here -- this is just rendering for our standard
970 | * polygons.
971 | *
972 | * Our start point is represented by a single moveto command (unless the
973 | * start point is curved) as the shape will be closed with the Z command
974 | * automatically if it is a closed shape. If we have a control point, we
975 | * have to go ahead and draw the curve.
976 | */
977 | if (($startPoint->flags & A2S_Point::CONTROL)) {
978 | $cX = $startPoint->x;
979 | $cY = $startPoint->y;
980 | $sX = $startPoint->x;
981 | $sY = $startPoint->y + 10;
982 | $eX = $startPoint->x + 10;
983 | $eY = $startPoint->y;
984 |
985 | $path = "M {$sX} {$sY} Q {$cX} {$cY} {$eX} {$eY} ";
986 | } else {
987 | $path = "M {$startPoint->x} {$startPoint->y} ";
988 | }
989 |
990 | $prevP = $startPoint;
991 | $bound = count($this->points);
992 | for ($i = 0; $i < $bound; $i++) {
993 | $p = $this->points[$i];
994 |
995 | /*
996 | * Handle quadratic Bezier curves. NOTE: This algorithm for drawing
997 | * the curves only works if the shapes are drawn in a clockwise
998 | * manner.
999 | */
1000 | if (($p->flags & A2S_Point::CONTROL)) {
1001 | /* Our control point is always the original corner */
1002 | $cX = $p->x;
1003 | $cY = $p->y;
1004 |
1005 | /* Need next point to determine which way to turn */
1006 | if ($i == count($this->points) - 1) {
1007 | $nP = $startPoint;
1008 | } else {
1009 | $nP = $this->points[$i + 1];
1010 | }
1011 |
1012 | if ($prevP->x == $p->x) {
1013 | /*
1014 | * If we are on the same vertical axis, our starting X coordinate
1015 | * is the same as the control point coordinate.
1016 | */
1017 | $sX = $p->x;
1018 |
1019 | /* Offset start point from control point in the proper direction */
1020 | if ($prevP->y < $p->y) {
1021 | $sY = $p->y - 10;
1022 | } else {
1023 | $sY = $p->y + 10;
1024 | }
1025 |
1026 | $eY = $p->y;
1027 | /* Offset end point from control point in the proper direction */
1028 | if ($nP->x < $p->x) {
1029 | $eX = $p->x - 10;
1030 | } else {
1031 | $eX = $p->x + 10;
1032 | }
1033 | } elseif ($prevP->y == $p->y) {
1034 | /* Horizontal decisions mirror vertical's above */
1035 | $sY = $p->y;
1036 | if ($prevP->x < $p->x) {
1037 | $sX = $p->x - 10;
1038 | } else {
1039 | $sX = $p->x + 10;
1040 | }
1041 |
1042 | $eX = $p->x;
1043 | if ($nP->y <= $p->y) {
1044 | $eY = $p->y - 10;
1045 | } else {
1046 | $eY = $p->y + 10;
1047 | }
1048 | }
1049 |
1050 | $path .= "L {$sX} {$sY} Q {$cX} {$cY} {$eX} {$eY} ";
1051 | } else {
1052 | /* The excruciating difficulty of drawing a straight line */
1053 | $path .= "L {$p->x} {$p->y} ";
1054 | }
1055 |
1056 | $prevP = $p;
1057 | }
1058 |
1059 | if ($this->isClosed()) {
1060 | $path .= 'Z';
1061 | }
1062 |
1063 | $id = self::$id++;
1064 |
1065 | /* Add markers if necessary. */
1066 | if ($startPoint->flags & A2S_Point::SMARKER) {
1067 | $this->options["marker-start"] = "url(#Pointer)";
1068 | } elseif ($startPoint->flags & A2S_Point::IMARKER) {
1069 | $this->options["marker-start"] = "url(#iPointer)";
1070 | }
1071 |
1072 | if ($endPoint->flags & A2S_Point::SMARKER) {
1073 | $this->options["marker-end"] = "url(#Pointer)";
1074 | } elseif ($endPoint->flags & A2S_Point::IMARKER) {
1075 | $this->options["marker-end"] = "url(#iPointer)";
1076 | }
1077 |
1078 | /*
1079 | * SVG objects without a fill will be transparent, and this looks so
1080 | * terrible with the drop-shadow effect. Any objects that aren't filled
1081 | * automatically get a white fill.
1082 | */
1083 | if ($this->isClosed() && !isset($this->options['fill'])) {
1084 | $this->options['fill'] = '#fff';
1085 | }
1086 |
1087 |
1088 | $out .= "\tname}\" ";
1089 | foreach ($this->options as $opt => $val) {
1090 | if (strpos($opt, 'a2s:', 0) === 0) {
1091 | continue;
1092 | }
1093 | $out .= "$opt=\"$val\" ";
1094 | }
1095 | $out .= "d=\"{$path}\" />\n";
1096 |
1097 | if (count($this->text) > 0) {
1098 | foreach ($this->text as $text) {
1099 | $text->setID($this->name);
1100 | $out .= "\t" . $text->render() . "\n";
1101 | }
1102 | }
1103 |
1104 | $bound = count($this->ticks);
1105 | for ($i = 0; $i < $bound; $i++) {
1106 | $t = $this->ticks[$i];
1107 | if ($t->flags & A2S_Point::DOT) {
1108 | $out .= "x}\" cy=\"{$t->y}\" r=\"3\" fill=\"black\" />";
1109 | } elseif ($t->flags & A2S_Point::TICK) {
1110 | $x1 = $t->x - 4;
1111 | $y1 = $t->y - 4;
1112 | $x2 = $t->x + 4;
1113 | $y2 = $t->y + 4;
1114 | $out .= "";
1115 |
1116 | $x1 = $t->x + 4;
1117 | $y1 = $t->y - 4;
1118 | $x2 = $t->x - 4;
1119 | $y2 = $t->y + 4;
1120 | $out .= "";
1121 | }
1122 | }
1123 |
1124 | $out .= "\n";
1125 | return $out;
1126 | }
1127 | }
1128 |
1129 | /*
1130 | * Nothing really special here. Container for representing text bits.
1131 | */
1132 | class A2S_SVGText {
1133 | private $options;
1134 | private $string;
1135 | private $point;
1136 | private $name;
1137 |
1138 | private static $id = 0;
1139 |
1140 | public function __construct($x, $y) {
1141 | $this->point = new A2S_Point($x, $y);
1142 | $this->name = self::$id++;
1143 | $this->options = array();
1144 | }
1145 |
1146 | public function setOption($opt, $val) {
1147 | $this->options[$opt] = $val;
1148 | }
1149 |
1150 | public function setID($id) {
1151 | $this->name = str_replace(' ', '_', str_replace('"', '_', $id));
1152 | }
1153 |
1154 | public function getID() {
1155 | return $this->name;
1156 | }
1157 |
1158 | public function getPoint() {
1159 | return $this->point;
1160 | }
1161 |
1162 | public function setString($string) {
1163 | $this->string = $string;
1164 | }
1165 |
1166 | public function render() {
1167 | $out = "point->x}\" y=\"{$this->point->y}\" id=\"text{$this->name}\" ";
1168 | foreach ($this->options as $opt => $val) {
1169 | if (strpos($opt, 'a2s:', 0) === 0) {
1170 | continue;
1171 | }
1172 | $out .= "$opt=\"$val\" ";
1173 | }
1174 | $out .= ">";
1175 | $out .= htmlentities($this->string);
1176 | $out .= "\n";
1177 | return $out;
1178 | }
1179 | }
1180 |
1181 | /*
1182 | * Main class for parsing ASCII and constructing the SVG output based on the
1183 | * above classes.
1184 | */
1185 | class A2S_ASCIIToSVG {
1186 | public $blurDropShadow = true;
1187 | public $fontFamily = "Consolas,Monaco,Anonymous Pro,Anonymous,Bitstream Sans Mono,monospace";
1188 |
1189 | private $rawData;
1190 | private $grid;
1191 |
1192 | private $svgObjects;
1193 | private $clearCorners;
1194 |
1195 | /* Directions for traversing lines in our grid */
1196 | const DIR_UP = 0x1;
1197 | const DIR_DOWN = 0x2;
1198 | const DIR_LEFT = 0x4;
1199 | const DIR_RIGHT = 0x8;
1200 | const DIR_NE = 0x10;
1201 | const DIR_SE = 0x20;
1202 |
1203 | public function __construct($data) {
1204 | /* For debugging purposes */
1205 | $this->rawData = $data;
1206 |
1207 | A2S_CustomObjects::loadObjects();
1208 |
1209 | $this->clearCorners = array();
1210 |
1211 | /*
1212 | * Parse out any command references. These need to be at the bottom of the
1213 | * diagram due to the way they're removed. Format is:
1214 | * [identifier] optional-colon optional-spaces ({json-blob})\n
1215 | *
1216 | * The JSON blob may not contain objects as values or the regex will break.
1217 | */
1218 | $this->commands = array();
1219 | preg_match_all('/^\[([^\]]+)\]:?\s+({[^}]+?})/ims', $data, $matches);
1220 | $bound = count($matches[1]);
1221 | for ($i = 0; $i < $bound; $i++) {
1222 | $this->commands[$matches[1][$i]] = json_decode($matches[2][$i], true);
1223 | }
1224 |
1225 | $data = preg_replace('/^\[([^\]]+)\](:?)\s+.*/ims', '', $data);
1226 |
1227 | /*
1228 | * Treat our ASCII field as a grid and store each character as a point in
1229 | * that grid. The (0, 0) coordinate on this grid is top-left, just as it
1230 | * is in images.
1231 | */
1232 | $this->grid = explode("\n", $data);
1233 |
1234 | foreach ($this->grid as $k => $line) {
1235 | $this->grid[$k] = str_split($line);
1236 | }
1237 |
1238 | $this->svgObjects = new A2S_SVGGroup();
1239 | }
1240 |
1241 | /*
1242 | * This is kind of a stupid and hacky way to do this, but this allows setting
1243 | * the default scale of one grid space on the X and Y axes.
1244 | */
1245 | public function setDimensionScale($x, $y) {
1246 | $o = A2S_Scale::getInstance();
1247 | $o->setScale($x, $y);
1248 | }
1249 |
1250 | public function dump() {
1251 | var_export($this);
1252 | }
1253 |
1254 | /* Render out what we've done! */
1255 | public function render() {
1256 | $o = A2S_Scale::getInstance();
1257 |
1258 | /* Figure out how wide we need to make the canvas */
1259 | $canvasWidth = 0;
1260 | foreach($this->grid as $line) {
1261 | if (count($line) > $canvasWidth) {
1262 | $canvasWidth = count($line);
1263 | }
1264 | }
1265 |
1266 | $canvasWidth = $canvasWidth * $o->xScale;
1267 | $canvasHeight = count($this->grid) * $o->yScale;
1268 |
1269 | /*
1270 | * Boilerplate header with definitions that we might be using for markers
1271 | * and drop shadows.
1272 | */
1273 | $out = <<