1<?php
2/*
3 * This file is a modified version of ASCIIToSVG.php
4 *
5 * It also includes other slightly modified files from the
6 * ASCIIToSVG suite :
7 * svg-path.lex.php jlex.php, svg-path.php and colors.php
8 * only the <?php opening tag has been commented out.
9 *
10 * Schplurtz le Déboulonné did the modifications
11 */
12/*
13 * ASCIIToSVG.php: ASCII diagram -> SVG art generator.
14 * Copyright © 2012 Devon H. O'Dell <devon.odell@gmail.com>
15 * All rights reserved.
16 *
17 * Redistribution and use in source and binary forms, with or without
18 * modification, are permitted provided that the following conditions
19 * are met:
20 *
21 *  o Redistributions of source code must retain the above copyright notice,
22 *    this list of conditions and the following disclaimer.
23 *  o Redistributions in binary form must reproduce the above copyright notice,
24 *    this list of conditions and the following disclaimer in the documentation
25 *    and/or other materials provided with the distribution.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 *
39 */
40
41namespace dokuwiki\plugin\a2s;
42
43//<?php # vim:ft=php
44//<?php # vim:ts=2:sw=2:et:
45/*
46  Copyright 2006 Wez Furlong, OmniTI Computer Consulting, Inc.
47  Based on JLex which is:
48
49       JLEX COPYRIGHT NOTICE, LICENSE, AND DISCLAIMER
50  Copyright 1996-2000 by Elliot Joel Berk and C. Scott Ananian
51
52  Permission to use, copy, modify, and distribute this software and its
53  documentation for any purpose and without fee is hereby granted,
54  provided that the above copyright notice appear in all copies and that
55  both the copyright notice and this permission notice and warranty
56  disclaimer appear in supporting documentation, and that the name of
57  the authors or their employers not be used in advertising or publicity
58  pertaining to distribution of the software without specific, written
59  prior permission.
60
61  The authors and their employers disclaim all warranties with regard to
62  this software, including all implied warranties of merchantability and
63  fitness. In no event shall the authors or their employers be liable
64  for any special, indirect or consequential damages or any damages
65  whatsoever resulting from loss of use, data or profits, whether in an
66  action of contract, negligence or other tortious action, arising out
67  of or in connection with the use or performance of this software.
68  **************************************************************
69*/
70
71class A2S_JLexToken {
72  public $line;
73  public $col;
74  public $value;
75  public $type;
76
77  function __construct($type, $value = null, $line = null, $col = null) {
78    $this->line = $line;
79    $this->col = $col;
80    $this->value = $value;
81    $this->type = $type;
82  }
83}
84
85class A2S_JLexBase {
86  const YY_F = -1;
87  const YY_NO_STATE = -1;
88  const YY_NOT_ACCEPT = 0;
89  const YY_START = 1;
90  const YY_END = 2;
91  const YY_NO_ANCHOR = 4;
92  const YYEOF = -1;
93
94  protected $YY_BOL;
95  protected $YY_EOF;
96
97  protected $yy_reader;
98  protected $yy_buffer;
99  protected $yy_buffer_read;
100  protected $yy_buffer_index;
101  protected $yy_buffer_start;
102  protected $yy_buffer_end;
103  protected $yychar = 0;
104  protected $yycol = 0;
105  protected $yyline = 0;
106  protected $yy_at_bol;
107  protected $yy_lexical_state;
108  protected $yy_last_was_cr = false;
109  protected $yy_count_lines = false;
110  protected $yy_count_chars = false;
111  protected $yyfilename = null;
112
113  function __construct($stream) {
114    $this->yy_reader = $stream;
115    $meta = stream_get_meta_data($stream);
116    if (!isset($meta['uri'])) {
117      $this->yyfilename = '<<input>>';
118    } else {
119      $this->yyfilename = $meta['uri'];
120    }
121
122    $this->yy_buffer = "";
123    $this->yy_buffer_read = 0;
124    $this->yy_buffer_index = 0;
125    $this->yy_buffer_start = 0;
126    $this->yy_buffer_end = 0;
127    $this->yychar = 0;
128    $this->yyline = 1;
129    $this->yy_at_bol = true;
130  }
131
132  protected function yybegin($state) {
133    $this->yy_lexical_state = $state;
134  }
135
136  protected function yy_advance() {
137    if ($this->yy_buffer_index < $this->yy_buffer_read) {
138      if (!isset($this->yy_buffer[$this->yy_buffer_index])) {
139        return $this->YY_EOF;
140      }
141      return ord($this->yy_buffer[$this->yy_buffer_index++]);
142    }
143    if ($this->yy_buffer_start != 0) {
144      /* shunt */
145      $j = $this->yy_buffer_read - $this->yy_buffer_start;
146      $this->yy_buffer = substr($this->yy_buffer, $this->yy_buffer_start, $j);
147      $this->yy_buffer_end -= $this->yy_buffer_start;
148      $this->yy_buffer_start = 0;
149      $this->yy_buffer_read = $j;
150      $this->yy_buffer_index = $j;
151
152      $data = fread($this->yy_reader, 8192);
153      if ($data === false || !strlen($data)) return $this->YY_EOF;
154      $this->yy_buffer .= $data;
155      $this->yy_buffer_read .= strlen($data);
156    }
157
158    while ($this->yy_buffer_index >= $this->yy_buffer_read) {
159      $data = fread($this->yy_reader, 8192);
160      if ($data === false || !strlen($data)) return $this->YY_EOF;
161      $this->yy_buffer .= $data;
162      $this->yy_buffer_read .= strlen($data);
163    }
164    return ord($this->yy_buffer[$this->yy_buffer_index++]);
165  }
166
167  protected function yy_move_end() {
168    if ($this->yy_buffer_end > $this->yy_buffer_start &&
169        $this->yy_buffer[$this->yy_buffer_end-1] == "\n")
170      $this->yy_buffer_end--;
171    if ($this->yy_buffer_end > $this->yy_buffer_start &&
172        $this->yy_buffer[$this->yy_buffer_end-1] == "\r")
173      $this->yy_buffer_end--;
174  }
175
176  protected function yy_mark_start() {
177    if ($this->yy_count_lines || $this->yy_count_chars) {
178      if ($this->yy_count_lines) {
179        for ($i = $this->yy_buffer_start; $i < $this->yy_buffer_index; ++$i) {
180          if ("\n" == $this->yy_buffer[$i] && !$this->yy_last_was_cr) {
181            ++$this->yyline;
182            $this->yycol = 0;
183          }
184          if ("\r" == $this->yy_buffer[$i]) {
185            ++$yyline;
186            $this->yycol = 0;
187            $this->yy_last_was_cr = true;
188          } else {
189            $this->yy_last_was_cr = false;
190          }
191        }
192      }
193      if ($this->yy_count_chars) {
194        $this->yychar += $this->yy_buffer_index - $this->yy_buffer_start;
195        $this->yycol += $this->yy_buffer_index - $this->yy_buffer_start;
196      }
197    }
198    $this->yy_buffer_start = $this->yy_buffer_index;
199  }
200
201  protected function yy_mark_end() {
202    $this->yy_buffer_end = $this->yy_buffer_index;
203  }
204
205  protected function yy_to_mark() {
206    #echo "yy_to_mark: setting buffer index to ", $this->yy_buffer_end, "\n";
207    $this->yy_buffer_index = $this->yy_buffer_end;
208    $this->yy_at_bol = ($this->yy_buffer_end > $this->yy_buffer_start) &&
209                ("\r" == $this->yy_buffer[$this->yy_buffer_end-1] ||
210                 "\n" == $this->yy_buffer[$this->yy_buffer_end-1] ||
211                 2028 /* unicode LS */ == $this->yy_buffer[$this->yy_buffer_end-1] ||
212                 2029 /* unicode PS */ == $this->yy_buffer[$this->yy_buffer_end-1]);
213  }
214
215  protected function yytext() {
216    return substr($this->yy_buffer, $this->yy_buffer_start,
217          $this->yy_buffer_end - $this->yy_buffer_start);
218  }
219
220  protected function yylength() {
221    return $this->yy_buffer_end - $this->yy_buffer_start;
222  }
223
224  static $yy_error_string = array(
225    'INTERNAL' => "Error: internal error.\n",
226    'MATCH' => "Error: Unmatched input.\n"
227  );
228
229  protected function yy_error($code, $fatal) {
230    print self::$yy_error_string[$code];
231    flush();
232    if ($fatal) throw new Exception("JLex fatal error " . self::$yy_error_string[$code]);
233  }
234
235  /* creates an annotated token */
236  function createToken($type = null) {
237    if ($type === null) $type = $this->yytext();
238    $tok = new A2S_JLexToken($type);
239    $this->annotateToken($tok);
240    return $tok;
241  }
242
243  /* annotates a token with a value and source positioning */
244  function annotateToken(A2S_JLexToken $tok) {
245    $tok->value = $this->yytext();
246    $tok->col = $this->yycol;
247    $tok->line = $this->yyline;
248    $tok->filename = $this->yyfilename;
249  }
250}
251
252//<?php # vim:ts=2:sw=2:et:
253/* Driver template for the LEMON parser generator.
254** The author disclaims copyright to this source code.
255*/
256/* First off, code is included which follows the "include" declaration
257** in the input file. */
258
259
260/* The following structure represents a single element of the
261** parser's stack.  Information stored includes:
262**
263**   +  The state number for the parser at this level of the stack.
264**
265**   +  The value of the token stored at this level of the stack.
266**      (In other words, the "major" token.)
267**
268**   +  The semantic value stored at this level of the stack.  This is
269**      the information used by the action routines in the grammar.
270**      It is sometimes called the "minor" token.
271*/
272class A2S_SVGPathyyStackEntry {
273  var /* int */ $stateno;       /* The state-number */
274  var /* int */ $major;         /* The major token value.  This is the code
275                     ** number for the token at this stack level */
276  var $minor; /* The user-supplied minor token value.  This
277                     ** is the value of the token  */
278};
279
280/* The state of the parser is completely contained in an instance of
281** the following structure */
282class A2S_SVGPathParser {
283  var /* int */ $yyidx = -1;                    /* Index of top element in stack */
284  var /* int */ $yyerrcnt;                 /* Shifts left before out of the error */
285  // A2S_SVGPathARG_SDECL                /* A place to hold %extra_argument */
286  var /* yyStackEntry */ $yystack = array(
287  	/* of YYSTACKDEPTH elements */
288	);  /* The parser's stack */
289
290  var $yyTraceFILE = null;
291  var $yyTracePrompt = null;
292
293
294
295/* Next is all token values, in a form suitable for use by makeheaders.
296** This section will be null unless lemon is run with the -m switch.
297*/
298/*
299** These constants (all generated automatically by the parser generator)
300** specify the various kinds of tokens (terminals) that the parser
301** understands.
302**
303** Each symbol here is a terminal symbol in the grammar.
304*/
305  const TK_ANY =  1;
306  const TK_MCMD =  2;
307  const TK_ZCMD =  3;
308  const TK_LCMD =  4;
309  const TK_HCMD =  5;
310  const TK_VCMD =  6;
311  const TK_CCMD =  7;
312  const TK_SCMD =  8;
313  const TK_QCMD =  9;
314  const TK_TCMD = 10;
315  const TK_ACMD = 11;
316  const TK_POSNUM = 12;
317  const TK_FLAG = 13;
318  const TK_NEGNUM = 14;
319/* The next thing included is series of defines which control
320** various aspects of the generated parser.
321**    YYCODETYPE         is the data type used for storing terminal
322**                       and nonterminal numbers.  "unsigned char" is
323**                       used if there are fewer than 250 terminals
324**                       and nonterminals.  "int" is used otherwise.
325**    YYNOCODE           is a number of type YYCODETYPE which corresponds
326**                       to no legal terminal or nonterminal number.  This
327**                       number is used to fill in empty slots of the hash
328**                       table.
329**    YYFALLBACK         If defined, this indicates that one or more tokens
330**                       have fall-back values which should be used if the
331**                       original value of the token will not parse.
332**    YYACTIONTYPE       is the data type used for storing terminal
333**                       and nonterminal numbers.  "unsigned char" is
334**                       used if there are fewer than 250 rules and
335**                       states combined.  "int" is used otherwise.
336**    A2S_SVGPathTOKENTYPE     is the data type used for minor tokens given
337**                       directly to the parser from the tokenizer.
338**    YYMINORTYPE        is the data type used for all minor tokens.
339**                       This is typically a union of many types, one of
340**                       which is A2S_SVGPathTOKENTYPE.  The entry in the union
341**                       for base tokens is called "yy0".
342**    YYSTACKDEPTH       is the maximum depth of the parser's stack.
343**    A2S_SVGPathARG_SDECL     A static variable declaration for the %extra_argument
344**    A2S_SVGPathARG_PDECL     A parameter declaration for the %extra_argument
345**    A2S_SVGPathARG_STORE     Code to store %extra_argument into yypParser
346**    A2S_SVGPathARG_FETCH     Code to extract %extra_argument from yypParser
347**    YYNSTATE           the combined number of states.
348**    YYNRULE            the number of rules in the grammar
349**    YYERRORSYMBOL      is the code number of the error symbol.  If not
350**                       defined, then do no error processing.
351*/
352  const YYNOCODE = 48;
353  const YYWILDCARD = 1;
354#define A2S_SVGPathTOKENTYPE void*
355  const YYSTACKDEPTH = 100;
356  const YYNSTATE = 74;
357  const YYNRULE = 52;
358  const YYERRORSYMBOL = 15;
359
360  /* since we cant use expressions to initialize these as class
361   * constants, we do so during parser init. */
362  var $YY_NO_ACTION;
363  var $YY_ACCEPT_ACTION;
364  var $YY_ERROR_ACTION;
365
366/* Next are that tables used to determine what action to take based on the
367** current state and lookahead token.  These tables are used to implement
368** functions that take a state number and lookahead value and return an
369** action integer.
370**
371** Suppose the action integer is N.  Then the action is determined as
372** follows
373**
374**   0 <= N < YYNSTATE                  Shift N.  That is, push the lookahead
375**                                      token onto the stack and goto state N.
376**
377**   YYNSTATE <= N < YYNSTATE+YYNRULE   Reduce by rule N-YYNSTATE.
378**
379**   N == YYNSTATE+YYNRULE              A syntax error has occurred.
380**
381**   N == YYNSTATE+YYNRULE+1            The parser accepts its input.
382**
383**   N == YYNSTATE+YYNRULE+2            No such action.  Denotes unused
384**                                      slots in the yy_action[] table.
385**
386** The action table is constructed as a single large table named yy_action[].
387** Given state S and lookahead X, the action is computed as
388**
389**      yy_action[ yy_shift_ofst[S] + X ]
390**
391** If the index value yy_shift_ofst[S]+X is out of range or if the value
392** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S]
393** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table
394** and that yy_default[S] should be used instead.
395**
396** The formula above is for computing the action when the lookahead is
397** a terminal symbol.  If the lookahead is a non-terminal (as occurs after
398** a reduce action) then the yy_reduce_ofst[] array is used in place of
399** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
400** YY_SHIFT_USE_DFLT.
401**
402** The following are the tables generated in this section:
403**
404**  yy_action[]        A single table containing all actions.
405**  yy_lookahead[]     A table containing the lookahead for each entry in
406**                     yy_action.  Used to detect hash collisions.
407**  yy_shift_ofst[]    For each state, the offset into yy_action for
408**                     shifting terminals.
409**  yy_reduce_ofst[]   For each state, the offset into yy_action for
410**                     shifting non-terminals after a reduce.
411**  yy_default[]       Default action for each state.
412*/
413static $yy_action = array(
414 /*     0 */     2,   39,   44,   45,   46,   47,   48,   49,   50,   51,
415 /*    10 */    52,   43,   44,   45,   46,   47,   48,   49,   50,   51,
416 /*    20 */    52,   53,    7,   20,   16,    4,    5,    3,    9,   26,
417 /*    30 */    21,   17,   22,   22,   10,   67,   35,   12,   22,    8,
418 /*    40 */    73,   42,    1,   56,   56,   14,   18,   22,   34,   56,
419 /*    50 */    22,   11,   70,   40,   19,   30,   63,   22,   56,   15,
420 /*    60 */    60,   56,   22,   14,   21,   22,   22,   56,   56,   65,
421 /*    70 */    68,   36,    6,   56,   32,  101,   56,   56,   31,  127,
422 /*    80 */    25,   41,    1,   17,   27,   22,   57,   58,   59,   29,
423 /*    90 */    61,   22,   71,   24,   62,   13,   56,   22,   94,   94,
424 /*   100 */    94,   56,   56,   33,   37,   56,   22,  101,   56,   95,
425 /*   110 */    95,   95,  101,   66,   69,   22,   22,   56,   64,   23,
426 /*   120 */    54,   72,   22,   22,   55,  101,   56,   56,  101,   56,
427 /*   130 */   101,  101,  101,   56,   56,   56,   28,   38,
428);
429static $yy_lookahead = array(
430 /*     0 */    20,   21,   22,   23,   24,   25,   26,   27,   28,   29,
431 /*    10 */    30,   21,   22,   23,   24,   25,   26,   27,   28,   29,
432 /*    20 */    30,    3,    4,    5,    6,    7,    8,    9,   10,   11,
433 /*    30 */    33,   33,   35,   35,   37,   38,   33,   13,   35,   41,
434 /*    40 */    42,   18,   19,   46,   46,   33,   43,   35,   33,   46,
435 /*    50 */    35,   39,   40,   31,   32,   33,   35,   35,   46,   32,
436 /*    60 */    33,   46,   35,   33,   33,   35,   35,   46,   46,   38,
437 /*    70 */    40,   45,    2,   46,   46,   47,   46,   46,   12,   16,
438 /*    80 */    17,   18,   19,   33,   12,   35,   12,   13,   14,   33,
439 /*    90 */    35,   35,   42,   34,   35,   33,   46,   35,   12,   13,
440 /*   100 */    14,   46,   46,   13,   33,   46,   35,   47,   46,   12,
441 /*   110 */    13,   14,   47,   33,   33,   35,   35,   46,   35,   36,
442 /*   120 */    33,   33,   35,   35,   35,   47,   46,   46,   47,   46,
443 /*   130 */    47,   47,   47,   46,   46,   46,   44,   45,
444);
445  const YY_SHIFT_USE_DFLT = -1;
446  const YY_SHIFT_MAX = 33;
447static $yy_shift_ofst = array(
448 /*     0 */    70,   18,   18,   74,   74,   74,   74,   74,   74,   74,
449 /*    10 */    74,   74,   74,   74,   74,   74,   74,   74,   74,   74,
450 /*    20 */    74,   74,   74,   74,   74,   70,   66,   74,   66,   86,
451 /*    30 */    97,   72,   90,   24,
452);
453  const YY_REDUCE_USE_DFLT = -21;
454  const YY_REDUCE_MAX = 28;
455static $yy_reduce_ofst = array(
456 /*     0 */    63,  -20,  -10,   -2,   -3,   12,   22,   27,   50,    3,
457 /*    10 */    31,   30,   71,   80,   81,   87,   83,   88,   15,   56,
458 /*    20 */    59,   62,   89,   21,   55,   23,   92,   28,   26,
459);
460static $yy_default = array(
461 /*     0 */   126,  126,   77,  126,  126,  126,  126,  126,  110,  126,
462 /*    10 */   102,  106,  126,  126,  126,   93,  126,  126,  114,  126,
463 /*    20 */   126,  126,  126,   99,   96,   74,  126,  126,  117,   90,
464 /*    30 */    91,  126,  126,  126,  115,  116,  118,  120,  119,   79,
465 /*    40 */    89,   76,   75,   78,   80,   81,   82,   83,   84,   85,
466 /*    50 */    86,   87,   88,   92,   94,  121,  122,  123,  124,  125,
467 /*    60 */    95,   97,   98,  100,  101,  103,  105,  104,  107,  109,
468 /*    70 */   108,  111,  113,  112,
469);
470
471/* The next table maps tokens into fallback tokens.  If a construct
472** like the following:
473**
474**      %fallback ID X Y Z.
475**
476** appears in the grammer, then ID becomes a fallback token for X, Y,
477** and Z.  Whenever one of the tokens X, Y, or Z is input to the parser
478** but it does not parse, the type of the token is changed to ID and
479** the parse is retried before an error is thrown.
480*/
481static $yyFallback = array(
482);
483
484/*
485** Turn parser tracing on by giving a stream to which to write the trace
486** and a prompt to preface each trace message.  Tracing is turned off
487** by making either argument NULL
488**
489** Inputs:
490** <ul>
491** <li> A FILE* to which trace output should be written.
492**      If NULL, then tracing is turned off.
493** <li> A prefix string written at the beginning of every
494**      line of trace output.  If NULL, then tracing is
495**      turned off.
496** </ul>
497**
498** Outputs:
499** None.
500*/
501function A2S_SVGPathTrace(/* stream */ $TraceFILE, /* string */ $zTracePrompt){
502  $this->yyTraceFILE = $TraceFILE;
503  $this->yyTracePrompt = $zTracePrompt;
504  if( $this->yyTraceFILE===null ) $this->yyTracePrompt = null;
505  else if( $this->yyTracePrompt===null ) $this->yyTraceFILE = null;
506}
507
508/* For tracing shifts, the names of all terminals and nonterminals
509** are required.  The following table supplies these names */
510static $yyTokenName = array(
511  '$',             'ANY',           'MCMD',          'ZCMD',
512  'LCMD',          'HCMD',          'VCMD',          'CCMD',
513  'SCMD',          'QCMD',          'TCMD',          'ACMD',
514  'POSNUM',        'FLAG',          'NEGNUM',        'error',
515  'svg_path',      'moveto_drawto_command_groups',  'moveto_drawto_command_group',  'moveto',
516  'drawto_commands',  'drawto_command',  'closepath',     'lineto',
517  'horizontal_lineto',  'vertical_lineto',  'curveto',       'smooth_curveto',
518  'quadratic_bezier_curveto',  'smooth_quadratic_bezier_curveto',  'elliptical_arc',  'moveto_argument_sequence',
519  'lineto_argument_sequence',  'coordinate_pair',  'horizontal_lineto_argument_sequence',  'coordinate',
520  'vertical_lineto_argument_sequence',  'curveto_argument_sequence',  'curveto_argument',  'smooth_curveto_argument_sequence',
521  'smooth_curveto_argument',  'quadratic_bezier_curveto_argument_sequence',  'quadratic_bezier_curveto_argument',  'smooth_quadratic_bezier_curveto_argument_sequence',
522  'elliptical_arc_argument_sequence',  'elliptical_arc_argument',  'number',
523);
524
525/* For tracing reduce actions, the names of all rules are required.
526*/
527static $yyRuleName = array(
528 /*   0 */ "svg_path ::= moveto_drawto_command_groups",
529 /*   1 */ "moveto_drawto_command_groups ::= moveto_drawto_command_groups moveto_drawto_command_group",
530 /*   2 */ "moveto_drawto_command_groups ::= moveto_drawto_command_group",
531 /*   3 */ "moveto_drawto_command_group ::= moveto drawto_commands",
532 /*   4 */ "drawto_commands ::= drawto_commands drawto_command",
533 /*   5 */ "drawto_commands ::= drawto_command",
534 /*   6 */ "drawto_command ::= closepath",
535 /*   7 */ "drawto_command ::= lineto",
536 /*   8 */ "drawto_command ::= horizontal_lineto",
537 /*   9 */ "drawto_command ::= vertical_lineto",
538 /*  10 */ "drawto_command ::= curveto",
539 /*  11 */ "drawto_command ::= smooth_curveto",
540 /*  12 */ "drawto_command ::= quadratic_bezier_curveto",
541 /*  13 */ "drawto_command ::= smooth_quadratic_bezier_curveto",
542 /*  14 */ "drawto_command ::= elliptical_arc",
543 /*  15 */ "moveto ::= MCMD moveto_argument_sequence",
544 /*  16 */ "moveto_argument_sequence ::= lineto_argument_sequence coordinate_pair",
545 /*  17 */ "moveto_argument_sequence ::= coordinate_pair",
546 /*  18 */ "closepath ::= ZCMD",
547 /*  19 */ "lineto ::= LCMD lineto_argument_sequence",
548 /*  20 */ "lineto_argument_sequence ::= lineto_argument_sequence coordinate_pair",
549 /*  21 */ "lineto_argument_sequence ::= coordinate_pair",
550 /*  22 */ "horizontal_lineto ::= HCMD horizontal_lineto_argument_sequence",
551 /*  23 */ "horizontal_lineto_argument_sequence ::= horizontal_lineto_argument_sequence coordinate",
552 /*  24 */ "horizontal_lineto_argument_sequence ::= coordinate",
553 /*  25 */ "vertical_lineto ::= VCMD vertical_lineto_argument_sequence",
554 /*  26 */ "vertical_lineto_argument_sequence ::= vertical_lineto_argument_sequence coordinate",
555 /*  27 */ "vertical_lineto_argument_sequence ::= coordinate",
556 /*  28 */ "curveto ::= CCMD curveto_argument_sequence",
557 /*  29 */ "curveto_argument_sequence ::= curveto_argument_sequence curveto_argument",
558 /*  30 */ "curveto_argument_sequence ::= curveto_argument",
559 /*  31 */ "curveto_argument ::= coordinate_pair coordinate_pair coordinate_pair",
560 /*  32 */ "smooth_curveto ::= SCMD smooth_curveto_argument_sequence",
561 /*  33 */ "smooth_curveto_argument_sequence ::= smooth_curveto_argument_sequence smooth_curveto_argument",
562 /*  34 */ "smooth_curveto_argument_sequence ::= smooth_curveto_argument",
563 /*  35 */ "smooth_curveto_argument ::= coordinate_pair coordinate_pair",
564 /*  36 */ "quadratic_bezier_curveto ::= QCMD quadratic_bezier_curveto_argument_sequence",
565 /*  37 */ "quadratic_bezier_curveto_argument_sequence ::= quadratic_bezier_curveto_argument_sequence quadratic_bezier_curveto_argument",
566 /*  38 */ "quadratic_bezier_curveto_argument_sequence ::= quadratic_bezier_curveto_argument",
567 /*  39 */ "quadratic_bezier_curveto_argument ::= coordinate_pair coordinate_pair",
568 /*  40 */ "smooth_quadratic_bezier_curveto ::= TCMD smooth_quadratic_bezier_curveto_argument_sequence",
569 /*  41 */ "smooth_quadratic_bezier_curveto_argument_sequence ::= smooth_quadratic_bezier_curveto_argument_sequence coordinate_pair",
570 /*  42 */ "smooth_quadratic_bezier_curveto_argument_sequence ::= coordinate_pair",
571 /*  43 */ "elliptical_arc ::= ACMD elliptical_arc_argument_sequence",
572 /*  44 */ "elliptical_arc_argument_sequence ::= elliptical_arc_argument_sequence elliptical_arc_argument",
573 /*  45 */ "elliptical_arc_argument_sequence ::= elliptical_arc_argument",
574 /*  46 */ "elliptical_arc_argument ::= POSNUM POSNUM number FLAG FLAG coordinate_pair",
575 /*  47 */ "coordinate_pair ::= coordinate coordinate",
576 /*  48 */ "coordinate ::= number",
577 /*  49 */ "number ::= POSNUM",
578 /*  50 */ "number ::= FLAG",
579 /*  51 */ "number ::= NEGNUM",
580);
581
582/*
583** This function returns the symbolic name associated with a token
584** value.
585*/
586function A2S_SVGPathTokenName(/* int */ $tokenType){
587  if (isset(self::$yyTokenName[$tokenType]))
588    return self::$yyTokenName[$tokenType];
589  return "Unknown";
590}
591
592/* The following function deletes the value associated with a
593** symbol.  The symbol can be either a terminal or nonterminal.
594** "yymajor" is the symbol code, and "yypminor" is a pointer to
595** the value.
596*/
597private function yy_destructor($yymajor, $yypminor){
598  switch( $yymajor ){
599    /* Here is inserted the actions which take place when a
600    ** terminal or non-terminal is destroyed.  This can happen
601    ** when the symbol is popped from the stack during a
602    ** reduce or during error processing or when a parser is
603    ** being destroyed before it is finished parsing.
604    **
605    ** Note: during a reduce, the only symbols destroyed are those
606    ** which appear on the RHS of the rule, but which are not used
607    ** inside the C code.
608    */
609    default:  break;   /* If no destructor action specified: do nothing */
610  }
611}
612
613/*
614** Pop the parser's stack once.
615**
616** If there is a destructor routine associated with the token which
617** is popped from the stack, then call it.
618**
619** Return the major token number for the symbol popped.
620*/
621private function yy_pop_parser_stack() {
622  if ($this->yyidx < 0) return 0;
623  $yytos = $this->yystack[$this->yyidx];
624  if( $this->yyTraceFILE ) {
625    fprintf($this->yyTraceFILE,"%sPopping %s\n",
626      $this->yyTracePrompt,
627      self::$yyTokenName[$yytos->major]);
628  }
629  $this->yy_destructor( $yytos->major, $yytos->minor);
630  unset($this->yystack[$this->yyidx]);
631  $this->yyidx--;
632  return $yytos->major;
633}
634
635/*
636** Deallocate and destroy a parser.  Destructors are all called for
637** all stack elements before shutting the parser down.
638**
639** Inputs:
640** <ul>
641** <li>  A pointer to the parser.  This should be a pointer
642**       obtained from A2S_SVGPathAlloc.
643** <li>  A pointer to a function used to reclaim memory obtained
644**       from malloc.
645** </ul>
646*/
647function __destruct()
648{
649  while($this->yyidx >= 0)
650    $this->yy_pop_parser_stack();
651}
652
653/*
654** Find the appropriate action for a parser given the terminal
655** look-ahead token iLookAhead.
656**
657** If the look-ahead token is YYNOCODE, then check to see if the action is
658** independent of the look-ahead.  If it is, return the action, otherwise
659** return YY_NO_ACTION.
660*/
661private function yy_find_shift_action(
662  $iLookAhead     /* The look-ahead token */
663){
664  $i = 0;
665  $stateno = $this->yystack[$this->yyidx]->stateno;
666
667  if( $stateno>self::YY_SHIFT_MAX ||
668      ($i = self::$yy_shift_ofst[$stateno])==self::YY_SHIFT_USE_DFLT ){
669    return self::$yy_default[$stateno];
670  }
671  if( $iLookAhead==self::YYNOCODE ){
672    return $this->YY_NO_ACTION;
673  }
674  $i += $iLookAhead;
675  if( $i<0 || $i>=count(self::$yy_action) || self::$yy_lookahead[$i]!=$iLookAhead ){
676    if( $iLookAhead>0 ){
677      if (isset(self::$yyFallback[$iLookAhead]) &&
678        ($iFallback = self::$yyFallback[$iLookAhead]) != 0) {
679        if( $this->yyTraceFILE ){
680          fprintf($this->yyTraceFILE, "%sFALLBACK %s => %s\n",
681             $this->yyTracePrompt, self::$yyTokenName[$iLookAhead],
682             self::$yyTokenName[$iFallback]);
683        }
684        return $this->yy_find_shift_action($iFallback);
685      }
686      {
687        $j = $i - $iLookAhead + self::YYWILDCARD;
688        if( $j>=0 && $j<count(self::$yy_action) && self::$yy_lookahead[$j]==self::YYWILDCARD ){
689          if( $this->yyTraceFILE ){
690            fprintf($this->yyTraceFILE, "%sWILDCARD %s => %s\n",
691               $this->yyTracePrompt, self::$yyTokenName[$iLookAhead],
692               self::$yyTokenName[self::YYWILDCARD]);
693          }
694          return self::$yy_action[$j];
695        }
696      }
697    }
698    return self::$yy_default[$stateno];
699  }else{
700    return self::$yy_action[$i];
701  }
702}
703
704/*
705** Find the appropriate action for a parser given the non-terminal
706** look-ahead token iLookAhead.
707**
708** If the look-ahead token is YYNOCODE, then check to see if the action is
709** independent of the look-ahead.  If it is, return the action, otherwise
710** return YY_NO_ACTION.
711*/
712private function yy_find_reduce_action(
713  $stateno,              /* Current state number */
714  $iLookAhead     /* The look-ahead token */
715){
716  $i = 0;
717
718  if( $stateno>self::YY_REDUCE_MAX ||
719      ($i = self::$yy_reduce_ofst[$stateno])==self::YY_REDUCE_USE_DFLT ){
720    return self::$yy_default[$stateno];
721  }
722  if( $iLookAhead==self::YYNOCODE ){
723    return $this->YY_NO_ACTION;
724  }
725  $i += $iLookAhead;
726  if( $i<0 || $i>=count(self::$yy_action) || self::$yy_lookahead[$i]!=$iLookAhead ){
727    return self::$yy_default[$stateno];
728  }else{
729    return self::$yy_action[$i];
730  }
731}
732
733/*
734** Perform a shift action.
735*/
736private function yy_shift(
737  $yyNewState,               /* The new state to shift in */
738  $yyMajor,                  /* The major token to shift in */
739  $yypMinor         /* Pointer ot the minor token to shift in */
740){
741  $this->yyidx++;
742  if (isset($this->yystack[$this->yyidx])) {
743    $yytos = $this->yystack[$this->yyidx];
744  } else {
745    $yytos = new A2S_SVGPathyyStackEntry;
746    $this->yystack[$this->yyidx] = $yytos;
747  }
748  $yytos->stateno = $yyNewState;
749  $yytos->major = $yyMajor;
750  $yytos->minor = $yypMinor;
751  if( $this->yyTraceFILE) {
752    fprintf($this->yyTraceFILE,"%sShift %d\n",$this->yyTracePrompt,$yyNewState);
753    fprintf($this->yyTraceFILE,"%sStack:",$this->yyTracePrompt);
754    for ($i = 1; $i <= $this->yyidx; $i++) {
755      $ent = $this->yystack[$i];
756      fprintf($this->yyTraceFILE," %s",self::$yyTokenName[$ent->major]);
757    }
758    fprintf($this->yyTraceFILE,"\n");
759  }
760}
761
762private function __overflow_dead_code() {
763  /* if the stack can overflow (it can't in the PHP implementation)
764   * Then the following code would be emitted */
765}
766
767/* The following table contains information about every rule that
768** is used during the reduce.
769** Rather than pollute memory with a large number of arrays,
770** we store both data points in the same array, indexing by
771** rule number * 2.
772static const struct {
773  YYCODETYPE lhs;         // Symbol on the left-hand side of the rule
774  unsigned char nrhs;     // Number of right-hand side symbols in the rule
775} yyRuleInfo[] = {
776*/
777static $yyRuleInfo = array(
778  16, 1,
779  17, 2,
780  17, 1,
781  18, 2,
782  20, 2,
783  20, 1,
784  21, 1,
785  21, 1,
786  21, 1,
787  21, 1,
788  21, 1,
789  21, 1,
790  21, 1,
791  21, 1,
792  21, 1,
793  19, 2,
794  31, 2,
795  31, 1,
796  22, 1,
797  23, 2,
798  32, 2,
799  32, 1,
800  24, 2,
801  34, 2,
802  34, 1,
803  25, 2,
804  36, 2,
805  36, 1,
806  26, 2,
807  37, 2,
808  37, 1,
809  38, 3,
810  27, 2,
811  39, 2,
812  39, 1,
813  40, 2,
814  28, 2,
815  41, 2,
816  41, 1,
817  42, 2,
818  29, 2,
819  43, 2,
820  43, 1,
821  30, 2,
822  44, 2,
823  44, 1,
824  45, 6,
825  33, 2,
826  35, 1,
827  46, 1,
828  46, 1,
829  46, 1,
830);
831
832/*
833** Perform a reduce action and the shift that must immediately
834** follow the reduce.
835*/
836private function yy_reduce(
837  $yyruleno                 /* Number of the rule by which to reduce */
838){
839  $yygoto = 0;                     /* The next state */
840  $yyact = 0;                      /* The next action */
841  $yygotominor = null;        /* The LHS of the rule reduced */
842  $yymsp = null;            /* The top of the parser's stack */
843  $yysize = 0;                     /* Amount to pop the stack */
844
845  $yymsp = $this->yystack[$this->yyidx];
846  if( $this->yyTraceFILE && isset(self::$yyRuleName[$yyruleno])) {
847    fprintf($this->yyTraceFILE, "%sReduce [%s].\n", $this->yyTracePrompt,
848      self::$yyRuleName[$yyruleno]);
849  }
850
851  switch( $yyruleno ){
852  /* Beginning here are the reduction cases.  A typical example
853  ** follows:
854  **   case 0:
855  **  #line <lineno> <grammarfile>
856  **     { ... }           // User supplied code
857  **  #line <lineno> <thisfile>
858  **     break;
859  */
860      case 15:
861#line 26 "svg-path.y"
862{
863		if (count($this->yystack[$this->yyidx + 0]->minor) == 2) {
864			$this->commands[] = array_merge(array($this->yystack[$this->yyidx + -1]->minor), $this->yystack[$this->yyidx + 0]->minor);
865		} else {
866			if ($this->yystack[$this->yyidx + -1]->minor->value == 'm') {
867				$arr = array ('value' => 'l');
868			} else {
869				$arr = array ('value' => 'L');
870			}
871			$c = array_splice($this->yystack[$this->yyidx + 0]->minor, 2);
872			$this->commands[] = array_merge(array($this->yystack[$this->yyidx + -1]->minor), $this->yystack[$this->yyidx + 0]->minor);
873			$this->commands[] = array_merge(array($arr), $c);
874		}
875	}
876#line 604 "svg-path.php"
877        break;
878      case 16:
879      case 20:
880      case 29:
881      case 33:
882      case 35:
883      case 37:
884      case 39:
885      case 41:
886      case 44:
887#line 42 "svg-path.y"
888{ $yygotominor = array_merge($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
889#line 617 "svg-path.php"
890        break;
891      case 17:
892      case 21:
893      case 30:
894      case 34:
895      case 38:
896      case 42:
897      case 45:
898      case 48:
899      case 49:
900      case 50:
901      case 51:
902#line 43 "svg-path.y"
903{ $yygotominor = $this->yystack[$this->yyidx + 0]->minor; }
904#line 632 "svg-path.php"
905        break;
906      case 18:
907#line 45 "svg-path.y"
908{ $this->commands[] = array($this->yystack[$this->yyidx + 0]->minor); }
909#line 637 "svg-path.php"
910        break;
911      case 19:
912      case 22:
913      case 25:
914      case 28:
915      case 32:
916      case 36:
917      case 40:
918      case 43:
919#line 48 "svg-path.y"
920{ $this->commands[] = array_merge(array($this->yystack[$this->yyidx + -1]->minor), $this->yystack[$this->yyidx + 0]->minor); }
921#line 649 "svg-path.php"
922        break;
923      case 23:
924      case 26:
925#line 59 "svg-path.y"
926{ $yygotominor = array_merge($this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + 0]->minor)); }
927#line 655 "svg-path.php"
928        break;
929      case 24:
930      case 27:
931#line 60 "svg-path.y"
932{ $yygotominor = array($this->yystack[$this->yyidx + 0]->minor); }
933#line 661 "svg-path.php"
934        break;
935      case 31:
936#line 80 "svg-path.y"
937{ $yygotominor = array_merge($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
938#line 666 "svg-path.php"
939        break;
940      case 46:
941#line 131 "svg-path.y"
942{ $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); }
943#line 671 "svg-path.php"
944        break;
945      case 47:
946#line 133 "svg-path.y"
947{ $yygotominor = array($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); }
948#line 676 "svg-path.php"
949        break;
950  };
951  $yygoto = self::$yyRuleInfo[2*$yyruleno];
952  $yysize = self::$yyRuleInfo[(2*$yyruleno)+1];
953
954  $state_for_reduce = $this->yystack[$this->yyidx - $yysize]->stateno;
955
956  $this->yyidx -= $yysize;
957  $yyact = $this->yy_find_reduce_action($state_for_reduce,$yygoto);
958  if( $yyact < self::YYNSTATE ){
959    $this->yy_shift($yyact, $yygoto, $yygotominor);
960  }else if( $yyact == self::YYNSTATE + self::YYNRULE + 1 ){
961    $this->yy_accept();
962  }
963}
964
965/*
966** The following code executes when the parse fails
967*/
968private function yy_parse_failed(
969){
970  if( $this->yyTraceFILE ){
971    fprintf($this->yyTraceFILE,"%sFail!\n",$this->yyTracePrompt);
972  }
973  while( $this->yyidx>=0 ) $this->yy_pop_parser_stack();
974  /* Here code is inserted which will be executed whenever the
975  ** parser fails */
976}
977
978/*
979** The following code executes when a syntax error first occurs.
980*/
981private function yy_syntax_error(
982  $yymajor,                   /* The major type of the error token */
983  $yyminor            /* The minor type of the error token */
984){
985}
986
987/*
988** The following is executed when the parser accepts
989*/
990private function yy_accept(
991){
992  if( $this->yyTraceFILE ){
993    fprintf($this->yyTraceFILE,"%sAccept!\n",$this->yyTracePrompt);
994  }
995  while( $this->yyidx>=0 ) $this->yy_pop_parser_stack();
996  /* Here code is inserted which will be executed whenever the
997  ** parser accepts */
998}
999
1000/* The main parser program.
1001** The first argument is a pointer to a structure obtained from
1002** "A2S_SVGPathAlloc" which describes the current state of the parser.
1003** The second argument is the major token number.  The third is
1004** the minor token.  The fourth optional argument is whatever the
1005** user wants (and specified in the grammar) and is available for
1006** use by the action routines.
1007**
1008** Inputs:
1009** <ul>
1010** <li> A pointer to the parser (an opaque structure.)
1011** <li> The major token number.
1012** <li> The minor token number.
1013** <li> An option argument of a grammar-specified type.
1014** </ul>
1015**
1016** Outputs:
1017** None.
1018*/
1019function A2S_SVGPath(
1020  $yymajor,                 /* The major token code number */
1021  $yyminor = null           /* The value for the token */
1022){
1023  $yyact = 0;            /* The parser action. */
1024  $yyendofinput = 0;     /* True if we are at the end of input */
1025  $yyerrorhit = 0;   /* True if yymajor has invoked an error */
1026
1027  /* (re)initialize the parser, if necessary */
1028  if( $this->yyidx<0 ){
1029    $this->yyidx = 0;
1030    $this->yyerrcnt = -1;
1031    $ent = new A2S_SVGPathyyStackEntry;
1032    $ent->stateno = 0;
1033    $ent->major = 0;
1034    $this->yystack = array( 0 => $ent );
1035
1036    $this->YY_NO_ACTION = self::YYNSTATE + self::YYNRULE + 2;
1037    $this->YY_ACCEPT_ACTION  = self::YYNSTATE + self::YYNRULE + 1;
1038    $this->YY_ERROR_ACTION   = self::YYNSTATE + self::YYNRULE;
1039  }
1040  $yyendofinput = ($yymajor==0);
1041
1042  if( $this->yyTraceFILE ){
1043    fprintf($this->yyTraceFILE,"%sInput %s\n",$this->yyTracePrompt,
1044      self::$yyTokenName[$yymajor]);
1045  }
1046
1047  do{
1048    $yyact = $this->yy_find_shift_action($yymajor);
1049    if( $yyact<self::YYNSTATE ){
1050      $this->yy_shift($yyact,$yymajor,$yyminor);
1051      $this->yyerrcnt--;
1052      if( $yyendofinput && $this->yyidx>=0 ){
1053        $yymajor = 0;
1054      }else{
1055        $yymajor = self::YYNOCODE;
1056      }
1057    }else if( $yyact < self::YYNSTATE + self::YYNRULE ){
1058      $this->yy_reduce($yyact-self::YYNSTATE);
1059    }else if( $yyact == $this->YY_ERROR_ACTION ){
1060      if( $this->yyTraceFILE ){
1061        fprintf($this->yyTraceFILE,"%sSyntax Error!\n",$this->yyTracePrompt);
1062      }
1063if (self::YYERRORSYMBOL) {
1064      /* A syntax error has occurred.
1065      ** The response to an error depends upon whether or not the
1066      ** grammar defines an error token "ERROR".
1067      **
1068      ** This is what we do if the grammar does define ERROR:
1069      **
1070      **  * Call the %syntax_error function.
1071      **
1072      **  * Begin popping the stack until we enter a state where
1073      **    it is legal to shift the error symbol, then shift
1074      **    the error symbol.
1075      **
1076      **  * Set the error count to three.
1077      **
1078      **  * Begin accepting and shifting new tokens.  No new error
1079      **    processing will occur until three tokens have been
1080      **    shifted successfully.
1081      **
1082      */
1083      if( $this->yyerrcnt<0 ){
1084        $this->yy_syntax_error($yymajor, $yyminor);
1085      }
1086      $yymx = $this->yystack[$this->yyidx]->major;
1087      if( $yymx==self::YYERRORSYMBOL || $yyerrorhit ){
1088        if( $this->yyTraceFILE ){
1089          fprintf($this->yyTraceFILE,"%sDiscard input token %s\n",
1090             $this->yyTracePrompt,self::$yyTokenName[$yymajor]);
1091        }
1092        $this->yy_destructor($yymajor,$yyminor);
1093        $yymajor = self::YYNOCODE;
1094      }else{
1095         while(
1096          $this->yyidx >= 0 &&
1097          $yymx != self::YYERRORSYMBOL &&
1098          ($yyact = $this->yy_find_reduce_action(
1099                        $this->yystack[$this->yyidx]->stateno,
1100                        self::YYERRORSYMBOL)) >= self::YYNSTATE
1101        ){
1102          $this->yy_pop_parser_stack();
1103        }
1104        if( $this->yyidx < 0 || $yymajor==0 ){
1105          $this->yy_destructor($yymajor,$yyminor);
1106          $this->yy_parse_failed();
1107          $yymajor = self::YYNOCODE;
1108        }else if( $yymx!=self::YYERRORSYMBOL ){
1109          $this->yy_shift($yyact,self::YYERRORSYMBOL,0);
1110        }
1111      }
1112      $this->yyerrcnt = 3;
1113      $yyerrorhit = 1;
1114} else {  /* YYERRORSYMBOL is not defined */
1115      /* This is what we do if the grammar does not define ERROR:
1116      **
1117      **  * Report an error message, and throw away the input token.
1118      **
1119      **  * If the input token is $, then fail the parse.
1120      **
1121      ** As before, subsequent error messages are suppressed until
1122      ** three input tokens have been successfully shifted.
1123      */
1124      if( $this->yyerrcnt<=0 ){
1125        $this->yy_syntax_error($yymajor, $yyminor);
1126      }
1127      $this->yyerrcnt = 3;
1128      $this->yy_destructor($yymajor,$yyminor);
1129      if( $yyendofinput ){
1130        $this->yy_parse_failed();
1131      }
1132      $yymajor = self::YYNOCODE;
1133}
1134    }else{
1135      $this->yy_accept();
1136      $yymajor = self::YYNOCODE;
1137    }
1138  }while( $yymajor!=self::YYNOCODE && $this->yyidx>=0 );
1139}
1140
1141}
1142
1143
1144class A2S_Yylex extends A2S_JLexBase  {
1145	const YY_BUFFER_SIZE = 512;
1146	const YY_F = -1;
1147	const YY_NO_STATE = -1;
1148	const YY_NOT_ACCEPT = 0;
1149	const YY_START = 1;
1150	const YY_END = 2;
1151	const YY_NO_ANCHOR = 4;
1152	const YY_BOL = 128;
1153	var $YY_EOF = 129;
1154
1155	/* w/e */
1156
1157	function __construct($stream) {
1158		parent::__construct($stream);
1159		$this->yy_lexical_state = self::YYINITIAL;
1160	}
1161
1162	const YYINITIAL = 0;
1163	static $yy_state_dtrans = array(
1164		0
1165	);
1166	static $yy_acpt = array(
1167		/* 0 */ self::YY_NOT_ACCEPT,
1168		/* 1 */ self::YY_NO_ANCHOR,
1169		/* 2 */ self::YY_NO_ANCHOR,
1170		/* 3 */ self::YY_NO_ANCHOR,
1171		/* 4 */ self::YY_NO_ANCHOR,
1172		/* 5 */ self::YY_NO_ANCHOR,
1173		/* 6 */ self::YY_NO_ANCHOR,
1174		/* 7 */ self::YY_NO_ANCHOR,
1175		/* 8 */ self::YY_NO_ANCHOR,
1176		/* 9 */ self::YY_NO_ANCHOR,
1177		/* 10 */ self::YY_NO_ANCHOR,
1178		/* 11 */ self::YY_NO_ANCHOR,
1179		/* 12 */ self::YY_NO_ANCHOR,
1180		/* 13 */ self::YY_NO_ANCHOR,
1181		/* 14 */ self::YY_NO_ANCHOR,
1182		/* 15 */ self::YY_NO_ANCHOR,
1183		/* 16 */ self::YY_NO_ANCHOR,
1184		/* 17 */ self::YY_NO_ANCHOR,
1185		/* 18 */ self::YY_NO_ANCHOR,
1186		/* 19 */ self::YY_NO_ANCHOR,
1187		/* 20 */ self::YY_NO_ANCHOR,
1188		/* 21 */ self::YY_NOT_ACCEPT,
1189		/* 22 */ self::YY_NO_ANCHOR,
1190		/* 23 */ self::YY_NO_ANCHOR,
1191		/* 24 */ self::YY_NO_ANCHOR,
1192		/* 25 */ self::YY_NO_ANCHOR,
1193		/* 26 */ self::YY_NO_ANCHOR,
1194		/* 27 */ self::YY_NO_ANCHOR,
1195		/* 28 */ self::YY_NOT_ACCEPT,
1196		/* 29 */ self::YY_NO_ANCHOR,
1197		/* 30 */ self::YY_NOT_ACCEPT,
1198		/* 31 */ self::YY_NO_ANCHOR,
1199		/* 32 */ self::YY_NOT_ACCEPT,
1200		/* 33 */ self::YY_NOT_ACCEPT,
1201		/* 34 */ self::YY_NOT_ACCEPT,
1202		/* 35 */ self::YY_NOT_ACCEPT,
1203		/* 36 */ self::YY_NOT_ACCEPT,
1204		/* 37 */ self::YY_NOT_ACCEPT,
1205		/* 38 */ self::YY_NOT_ACCEPT
1206	);
1207		static $yy_cmap = array(
1208 19, 19, 19, 19, 19, 19, 19, 19, 19, 18, 18, 19, 18, 18, 19, 19, 19, 19, 19, 19,
1209 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 18, 19, 19, 19, 19, 19, 19, 19,
1210 19, 19, 19, 2, 18, 5, 6, 19, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 19, 19,
1211 19, 19, 19, 19, 19, 17, 19, 14, 19, 7, 19, 19, 11, 19, 19, 19, 10, 8, 19, 19,
1212 19, 13, 19, 15, 16, 19, 12, 19, 19, 19, 9, 19, 19, 19, 19, 19, 19, 17, 19, 14,
1213 19, 7, 19, 19, 11, 19, 19, 19, 10, 8, 19, 19, 19, 13, 19, 15, 16, 19, 12, 19,
1214 19, 19, 9, 19, 1, 19, 19, 19, 0, 0,);
1215
1216		static $yy_rmap = array(
1217 0, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 5, 6, 7,
1218 8, 9, 3, 10, 11, 12, 13, 14, 15, 9, 16, 1, 17, 11, 18, 19, 12, 13, 14,);
1219
1220		static $yy_nxt = array(
1221array(
1222 1, 2, 3, 22, 4, 23, 29, 31, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 31,
1223
1224),
1225array(
1226 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1227
1228),
1229array(
1230 -1, -1, -1, 4, 4, -1, 21, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1231
1232),
1233array(
1234 -1, -1, -1, 4, 4, -1, 16, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1235
1236),
1237array(
1238 -1, -1, -1, 18, 18, -1, -1, 30, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1239
1240),
1241array(
1242 -1, -1, -1, 17, 17, -1, 19, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1243
1244),
1245array(
1246 -1, -1, -1, 18, 18, -1, -1, 32, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1247
1248),
1249array(
1250 -1, -1, -1, 20, 20, -1, -1, 34, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1251
1252),
1253array(
1254 -1, -1, -1, 20, 20, -1, -1, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1255
1256),
1257array(
1258 -1, -1, -1, 18, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1259
1260),
1261array(
1262 -1, -1, -1, 17, 17, -1, 28, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1263
1264),
1265array(
1266 -1, -1, -1, 24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1267
1268),
1269array(
1270 -1, -1, -1, 25, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1271
1272),
1273array(
1274 -1, -1, -1, 26, 26, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1275
1276),
1277array(
1278 -1, -1, -1, 27, 27, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1279
1280),
1281array(
1282 -1, -1, -1, 20, 20, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1283
1284),
1285array(
1286 -1, -1, 33, 24, 24, 33, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1287
1288),
1289array(
1290 -1, -1, 36, 25, 25, 36, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1291
1292),
1293array(
1294 -1, -1, 37, 26, 26, 37, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1295
1296),
1297array(
1298 -1, -1, 38, 27, 27, 38, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1299
1300),
1301);
1302
1303	public function /*Yytoken*/ nextToken ()
1304 {
1305		$yy_anchor = self::YY_NO_ANCHOR;
1306		$yy_state = self::$yy_state_dtrans[$this->yy_lexical_state];
1307		$yy_next_state = self::YY_NO_STATE;
1308		$yy_last_accept_state = self::YY_NO_STATE;
1309		$yy_initial = true;
1310
1311		$this->yy_mark_start();
1312		$yy_this_accept = self::$yy_acpt[$yy_state];
1313		if (self::YY_NOT_ACCEPT != $yy_this_accept) {
1314			$yy_last_accept_state = $yy_state;
1315			$this->yy_mark_end();
1316		}
1317		while (true) {
1318			if ($yy_initial && $this->yy_at_bol) $yy_lookahead = self::YY_BOL;
1319			else $yy_lookahead = $this->yy_advance();
1320			$yy_next_state = self::$yy_nxt[self::$yy_rmap[$yy_state]][self::$yy_cmap[$yy_lookahead]];
1321			if ($this->YY_EOF == $yy_lookahead && true == $yy_initial) {
1322				return null;
1323			}
1324			if (self::YY_F != $yy_next_state) {
1325				$yy_state = $yy_next_state;
1326				$yy_initial = false;
1327				$yy_this_accept = self::$yy_acpt[$yy_state];
1328				if (self::YY_NOT_ACCEPT != $yy_this_accept) {
1329					$yy_last_accept_state = $yy_state;
1330					$this->yy_mark_end();
1331				}
1332			}
1333			else {
1334				if (self::YY_NO_STATE == $yy_last_accept_state) {
1335					throw new Exception("Lexical Error: Unmatched Input.");
1336				}
1337				else {
1338					$yy_anchor = self::$yy_acpt[$yy_last_accept_state];
1339					if (0 != (self::YY_END & $yy_anchor)) {
1340						$this->yy_move_end();
1341					}
1342					$this->yy_to_mark();
1343					switch ($yy_last_accept_state) {
1344						case 1:
1345
1346						case -2:
1347							break;
1348						case 2:
1349							{ return $this->createToken(A2S_SVGPathParser::TK_FLAG); }
1350						case -3:
1351							break;
1352						case 3:
1353							{ }
1354						case -4:
1355							break;
1356						case 4:
1357							{ return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
1358						case -5:
1359							break;
1360						case 5:
1361							{ return $this->createToken(A2S_SVGPathParser::TK_MCMD); }
1362						case -6:
1363							break;
1364						case 6:
1365							{ return $this->createToken(A2S_SVGPathParser::TK_ZCMD); }
1366						case -7:
1367							break;
1368						case 7:
1369							{ return $this->createToken(A2S_SVGPathParser::TK_LCMD); }
1370						case -8:
1371							break;
1372						case 8:
1373							{ return $this->createToken(A2S_SVGPathParser::TK_HCMD); }
1374						case -9:
1375							break;
1376						case 9:
1377							{ return $this->createToken(A2S_SVGPathParser::TK_VCMD); }
1378						case -10:
1379							break;
1380						case 10:
1381							{ return $this->createToken(A2S_SVGPathParser::TK_QCMD); }
1382						case -11:
1383							break;
1384						case 11:
1385							{ return $this->createToken(A2S_SVGPathParser::TK_CCMD); }
1386						case -12:
1387							break;
1388						case 12:
1389							{ return $this->createToken(A2S_SVGPathParser::TK_SCMD); }
1390						case -13:
1391							break;
1392						case 13:
1393							{ return $this->createToken(A2S_SVGPathParser::TK_TCMD); }
1394						case -14:
1395							break;
1396						case 14:
1397							{ return $this->createToken(A2S_SVGPathParser::TK_ACMD); }
1398						case -15:
1399							break;
1400						case 15:
1401							{ }
1402						case -16:
1403							break;
1404						case 16:
1405							{ return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
1406						case -17:
1407							break;
1408						case 17:
1409							{ return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
1410						case -18:
1411							break;
1412						case 18:
1413							{ return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
1414						case -19:
1415							break;
1416						case 19:
1417							{ return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
1418						case -20:
1419							break;
1420						case 20:
1421							{ return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
1422						case -21:
1423							break;
1424						case 22:
1425							{ return $this->createToken(A2S_SVGPathParser::TK_FLAG); }
1426						case -22:
1427							break;
1428						case 23:
1429							{ }
1430						case -23:
1431							break;
1432						case 24:
1433							{ return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
1434						case -24:
1435							break;
1436						case 25:
1437							{ return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
1438						case -25:
1439							break;
1440						case 26:
1441							{ return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
1442						case -26:
1443							break;
1444						case 27:
1445							{ return $this->createToken(A2S_SVGPathParser::TK_POSNUM); }
1446						case -27:
1447							break;
1448						case 29:
1449							{ }
1450						case -28:
1451							break;
1452						case 31:
1453							{ }
1454						case -29:
1455							break;
1456						default:
1457						$this->yy_error('INTERNAL',false);
1458					case -1:
1459					}
1460					$yy_initial = true;
1461					$yy_state = self::$yy_state_dtrans[$this->yy_lexical_state];
1462					$yy_next_state = self::YY_NO_STATE;
1463					$yy_last_accept_state = self::YY_NO_STATE;
1464					$this->yy_mark_start();
1465					$yy_this_accept = self::$yy_acpt[$yy_state];
1466					if (self::YY_NOT_ACCEPT != $yy_this_accept) {
1467						$yy_last_accept_state = $yy_state;
1468						$this->yy_mark_end();
1469					}
1470				}
1471			}
1472		}
1473	}
1474}
1475//<?php
1476
1477$A2S_colors = array(
1478  "indian red"   => "#cd5c5c",
1479  "light coral"  => "#f08080",
1480  "salmon"       => "#fa8072",
1481  "dark salmon"  => "#e9967a",
1482  "light salmon" => "#ffa07a",
1483  "crimson"      => "#dc143c",
1484  "red"          => "#ff0000",
1485  "fire brick"   => "#b22222",
1486  "dark red"     => "#8b0000",
1487
1488  "pink"              => "#ffc0cb",
1489  "light pink"        => "#ffb6c1",
1490  "hot pink"          => "#ff69b4",
1491  "deep pink"         => "#ff1493",
1492  "medium violet red" => "#c71585",
1493  "pale violet red"   => "#db7093",
1494
1495  "light salmon" => "#ffa07a",
1496  "coral"        => "#ff7f50",
1497  "tomato"       => "#ff6347",
1498  "orange red"   => "#ff4500",
1499  "dark orange"  => "#ff8c00",
1500  "orange"       => "#ffa500",
1501
1502  "gold"                   => "#ffd700",
1503  "yellow"                 => "#ffff00",
1504  "light yellow"           => "#ffffe0",
1505  "lemon chiffon"          => "#fffacd",
1506  "light goldenrod yellow" => "#fafad2",
1507  "papaya whip"            => "#ffefd5",
1508  "moccasin"               => "#ffe4b5",
1509  "peach puff"             => "#ffdab9",
1510  "pale goldenrod"         => "#eee8aa",
1511  "khaki"                  => "#f0e68c",
1512  "dark khaki"             => "#bdb76b",
1513
1514  "lavender"        => "#e6e6fa",
1515  "thistle"         => "#d8bfd8",
1516  "plum"            => "#dda0dd",
1517  "violet"          => "#ee82ee",
1518  "orchid"          => "#da70d6",
1519  "fuchsia"         => "#ff00ff",
1520  "magenta"         => "#ff00ff",
1521  "medium orchid"   => "#ba55d3",
1522  "medium purple"   => "#9370db",
1523  "blue violet"     => "#8a2be2",
1524  "dark violet"     => "#9400d3",
1525  "dark orchid"     => "#9932cc",
1526  "dark magenta"    => "#8b008b",
1527  "purple"          => "#800080",
1528  "indigo"          => "#4b0082",
1529  "slate blue"      => "#6a5acd",
1530  "dark slate blue" => "#483d8b",
1531
1532  "green yellow"        => "#adff2f",
1533  "chartreuse"          => "#7fff00",
1534  "lawn green"          => "#7cfc00",
1535  "lime"                => "#00ff00",
1536  "lime green"          => "#32cd32",
1537  "pale green"          => "#98fb98",
1538  "light  green"        => "#90ee90",
1539  "medium spring green" => "#00fa9a",
1540  "spring green"        => "#00ff7f",
1541  "medium sea green"    => "#3cb371",
1542  "sea green"           => "#2e8b57",
1543  "forest green"        => "#228b22",
1544  "green"               => "#008000",
1545  "dark green"          => "#006400",
1546  "yellow green"        => "#9acd32",
1547  "olive drab"          => "#6b8e23",
1548  "olive"               => "#808000",
1549  "dark olive green"    => "#556b2f",
1550  "medium aquamarine"   => "#66cdaa",
1551  "dark sea green"      => "#8fbc8f",
1552  "light sea green"     => "#20b2aa",
1553  "dark cyan"           => "#008b8b",
1554  "teal"                => "#008080",
1555
1556  "aqua"              => "#00ffff",
1557  "cyan"              => "#00ffff",
1558  "light cyan"        => "#e0ffff",
1559  "pale turquoise"    => "#afeeee",
1560  "aquamarine"        => "#7fffd4",
1561  "turquoise"         => "#40e0d0",
1562  "medium turquoise"  => "#48d1cc",
1563  "dark turquoise"    => "#00ced1",
1564  "cadet blue"        => "#5f9ea0",
1565  "steel blue"        => "#4682b4",
1566  "light steel blue"  => "#b0c4de",
1567  "powder blue"       => "#b0e0e6",
1568  "light blue"        => "#add8e6",
1569  "sky blue"          => "#87ceeb",
1570  "light sky blue"    => "#87cefa",
1571  "deep sky blue"     => "#00bfff",
1572  "dodger blue"       => "#1e90ff",
1573  "cornflower blue"   => "#6495ed",
1574  "medium slate blue" => "#7b68ee",
1575  "royal blue"        => "#4169e1",
1576  "blue"              => "#0000ff",
1577  "medium blue"       => "#0000cd",
1578  "dark blue"         => "#00008b",
1579  "navy"              => "#000080",
1580  "midnight blue"     => "#191970",
1581
1582  "cornsilk"        => "#fff8dc",
1583  "blanched almond" => "#ffebcd",
1584  "bisque"          => "#ffe4c4",
1585  "navajo white"    => "#ffdead",
1586  "wheat"           => "#f5deb3",
1587  "burly wood"      => "#deb887",
1588  "tan"             => "#d2b48c",
1589  "rosy brown"      => "#bc8f8f",
1590  "sandy brown"     => "#f4a460",
1591  "goldenrod"       => "#daa520",
1592  "dark goldenrod"  => "#b8860b",
1593  "peru"            => "#cd853f",
1594  "chocolate"       => "#d2691e",
1595  "saddle brown"    => "#8b4513",
1596  "sienna"          => "#a0522d",
1597  "brown"           => "#a52a2a",
1598  "maroon"          => "#800000",
1599
1600  "white"          => "#ffffff",
1601  "snow"           => "#fffafa",
1602  "honeydew"       => "#f0fff0",
1603  "mint cream"     => "#f5fffa",
1604  "azure"          => "#f0ffff",
1605  "alice blue"     => "#f0f8ff",
1606  "ghost white"    => "#f8f8ff",
1607  "white smoke"    => "#f5f5f5",
1608  "seashell"       => "#fff5ee",
1609  "beige"          => "#f5f5dc",
1610  "old lace"       => "#fdf5e6",
1611  "floral white"   => "#fffaf0",
1612  "ivory"          => "#fffff0",
1613  "antique white"  => "#faebd7",
1614  "linen"          => "#faf0e6",
1615  "lavender blush" => "#fff0f5",
1616  "misty rose"     => "#ffe4e1",
1617
1618  "gainsboro"        => "#dcdcdc",
1619  "light grey"       => "#d3d3d3",
1620  "silver"           => "#c0c0c0",
1621  "dark gray"        => "#a9a9a9",
1622  "gray"             => "#808080",
1623  "dim gray"         => "#696969",
1624  "light slate gray" => "#778899",
1625  "slate gray"       => "#708090",
1626  "dark slate gray"  => "#2f4f4f",
1627  "black"            => "#000000"
1628);
1629
1630/* vim:ts=2:sw=2:et:
1631 *  * */
1632
1633/*
1634 * Scale is a singleton class that is instantiated to apply scale
1635 * transformations on the text -> canvas grid geometry. We could probably use
1636 * SVG's native scaling for this, but I'm not sure how yet.
1637 */
1638class Scale {
1639  private static $instance = null;
1640
1641  public $xScale;
1642  public $yScale;
1643
1644  private function __construct() {}
1645  private function __clone() {}
1646
1647  public static function getInstance() {
1648    if (self::$instance == null) {
1649      self::$instance = new Scale();
1650    }
1651
1652    return self::$instance;
1653  }
1654
1655  public function setScale($x, $y) {
1656    $o = self::getInstance();
1657    $o->xScale = $x;
1658    $o->yScale = $y;
1659  }
1660}
1661
1662/*
1663 * CustomObjects allows users to create their own custom SVG paths and use
1664 * them as box types with a2s:type references.
1665 *
1666 * Paths must have width and height set, and must not span multiple lines.
1667 * Multiple paths can be specified, one path per line. All objects must
1668 * reside in the same directory.
1669 *
1670 * File operations are horribly slow, so we make a best effort to avoid
1671 * as many as possible:
1672 *
1673 *  * If the directory mtime hasn't changed, we attempt to load our
1674 *    objects from a cache file.
1675 *
1676 *  * If this file doesn't exist, can't be read, or the mtime has
1677 *    changed, we scan the directory and update files that have changed
1678 *    based on their mtime.
1679 *
1680 *  * We attempt to save our cache in a temporary directory. It's volatile
1681 *    but also requires no configuration.
1682 *
1683 * We could do a bit better by utilizing APC's shared memory storage, which
1684 * would help greatly when running on a server.
1685 *
1686 * Note that the path parser isn't foolproof, mostly because PHP isn't the
1687 * greatest language ever for implementing a parser.
1688 */
1689class CustomObjects {
1690  public static $objects = array();
1691
1692  /*
1693   * Closures / callable function names / whatever for integrating non-default
1694   * loading and storage functionality.
1695   */
1696  public static $loadCacheFn = null;
1697  public static $storCacheFn = null;
1698  public static $loadObjsFn = null;
1699
1700
1701  public static function loadObjects() {
1702    global $conf;
1703    $cacheFile = $conf['cachedir'] . '/plugin.asciitosvg.objcache';
1704    $dir = dirname(__FILE__) . '/objects';
1705    if (is_callable(self::$loadCacheFn)) {
1706      /*
1707       * Should return exactly what was given to the $storCacheFn when it was
1708       * last called, or null if nothing can be loaded.
1709       */
1710      $fn = self::$loadCacheFn;
1711      self::$objects = $fn();
1712      return;
1713    } else {
1714      if (is_readable($cacheFile) && is_readable($dir)) {
1715        $cacheTime = filemtime($cacheFile);
1716
1717        if (filemtime($dir) <= filemtime($cacheFile)) {
1718          self::$objects = unserialize(file_get_contents($cacheFile));
1719          return;
1720        }
1721      } else if (file_exists($cacheFile)) {
1722        return;
1723      }
1724    }
1725
1726    if (is_callable(self::$loadObjsFn)) {
1727      /*
1728       * Returns an array of arrays of path information. The innermost arrays
1729       * (containing the path information) contain the path name, the width of
1730       * the bounding box, the height of the bounding box, and the path
1731       * command. This interface does *not* want the path's XML tag. An array
1732       * returned from here containing two objects that each have 1 line would
1733       * look like:
1734       *
1735       * array (
1736       *   array (
1737       *     name => 'pathA',
1738       *     paths => array (
1739       *       array ('width' => 10, 'height' => 10, 'path' => 'M 0 0 L 10 10'),
1740       *       array ('width' => 10, 'height' => 10, 'path' => 'M 0 10 L 10 0'),
1741       *     ),
1742       *   ),
1743       *   array (
1744       *     name => 'pathB',
1745       *     paths => array (
1746       *       array ('width' => 10, 'height' => 10, 'path' => 'M 0 5 L 5 10'),
1747       *       array ('width' => 10, 'height' => 10, 'path' => 'M 5 10 L 10 5'),
1748       *     ),
1749       *   ),
1750       * );
1751       */
1752      $fn = self::$loadObjsFn;
1753      $objs = $fn();
1754
1755      $i = 0;
1756      foreach ($objs as $obj) {
1757        foreach ($obj['paths'] as $path) {
1758          self::$objects[$obj['name']][$i]['width'] = $path['width'];
1759          self::$objects[$obj['name']][$i]['height'] = $path['height'];
1760          self::$objects[$obj['name']][$i++]['path'] =
1761            self::parsePath($path['path']);
1762        }
1763      }
1764    } else {
1765      $ents = scandir($dir);
1766      foreach ($ents as $ent) {
1767        $file = "{$dir}/{$ent}";
1768        $base = substr($ent, 0, -5);
1769        if (substr($ent, -5) == '.path' && is_readable($file)) {
1770          if (isset(self::$objects[$base]) &&
1771              filemtime($file) <= self::$cacheTime) {
1772            continue;
1773          }
1774
1775          $lines = file($file);
1776
1777          $i = 0;
1778          foreach ($lines as $line) {
1779            preg_match('/width="(\d+)/', $line, $m);
1780            $width = $m[1];
1781            preg_match('/height="(\d+)/', $line, $m);
1782            $height = $m[1];
1783            preg_match('/d="([^"]+)"/', $line, $m);
1784            $path = $m[1];
1785
1786            self::$objects[$base][$i]['width'] = $width;
1787            self::$objects[$base][$i]['height'] = $height;
1788            self::$objects[$base][$i++]['path'] = self::parsePath($path);
1789          }
1790        }
1791      }
1792    }
1793
1794    if (is_callable(self::$storCacheFn)) {
1795      $fn = self::$storCacheFn;
1796      $fn(self::$objects);
1797    } else {
1798      file_put_contents($cacheFile, serialize(self::$objects));
1799    }
1800  }
1801
1802  private static function parsePath($path) {
1803    $stream = fopen("data://text/plain,{$path}", 'r');
1804
1805    $P = new A2S_SVGPathParser();
1806    $S = new A2S_Yylex($stream);
1807
1808    while ($t = $S->nextToken()) {
1809      $P->A2S_SVGPath($t->type, $t);
1810    }
1811    /* Force shift/reduce of last token. */
1812    $P->A2S_SVGPath(0);
1813
1814    fclose($stream);
1815
1816    $cmdArr = array();
1817    $i = 0;
1818    foreach ($P->commands as $cmd) {
1819      foreach ($cmd as $arg) {
1820        $arg = (array)$arg;
1821        $cmdArr[$i][] = $arg['value'];
1822      }
1823      $i++;
1824    }
1825
1826    return $cmdArr;
1827  }
1828}
1829
1830/*
1831 * All lines and polygons are represented as a series of point coordinates
1832 * along a path. Points can have different properties; markers appear on
1833 * edges of lines and control points denote that a bezier curve should be
1834 * calculated for the corner represented by this point.
1835 */
1836class Point {
1837  public $gridX;
1838  public $gridY;
1839
1840  public $x;
1841  public $y;
1842
1843  public $flags;
1844
1845  const POINT    = 0x1;
1846  const CONTROL  = 0x2;
1847  const SMARKER  = 0x4;
1848  const IMARKER  = 0x8;
1849  const TICK     = 0x10;
1850  const DOT      = 0x20;
1851
1852  public function __construct($x, $y) {
1853    $this->flags = 0;
1854
1855    $s = Scale::getInstance();
1856    $this->x = ($x * $s->xScale) + ($s->xScale / 2);
1857    $this->y = ($y * $s->yScale) + ($s->yScale / 2);
1858
1859    $this->gridX = $x;
1860    $this->gridY = $y;
1861  }
1862}
1863
1864/*
1865 * Groups objects together and sets common properties for the objects in the
1866 * group.
1867 */
1868class SVGGroup {
1869  private $groups;
1870  private $curGroup;
1871  private $groupStack;
1872  private $options;
1873
1874  public function __construct() {
1875    $this->groups = array();
1876    $this->groupStack = array();
1877    $this->options = array();
1878  }
1879
1880  public function getGroup($groupName) {
1881    return $this->groups[$groupName];
1882  }
1883
1884  public function pushGroup($groupName) {
1885    if (!isset($this->groups[$groupName])) {
1886      $this->groups[$groupName] = array();
1887      $this->options[$groupName] = array();
1888    }
1889
1890    $this->groupStack[] = $groupName;
1891    $this->curGroup = $groupName;
1892  }
1893
1894  public function popGroup() {
1895    /*
1896     * Remove the last group and fetch the current one. array_pop will return
1897     * NULL for an empty array, so this is safe to do when only one element
1898     * is left.
1899     */
1900    array_pop($this->groupStack);
1901    $this->curGroup = array_pop($this->groupStack);
1902  }
1903
1904  public function addObject($o) {
1905    $this->groups[$this->curGroup][] = $o;
1906  }
1907
1908  public function setOption($opt, $val) {
1909    $this->options[$this->curGroup][$opt] = $val;
1910  }
1911
1912  public function render() {
1913    $out = '';
1914
1915    foreach($this->groups as $groupName => $objects) {
1916      $out .= "<g id=\"{$groupName}\" ";
1917      foreach ($this->options[$groupName] as $opt => $val) {
1918        if (strpos($opt, 'a2s:', 0) === 0) {
1919          continue;
1920        }
1921        $out .= "$opt=\"$val\" ";
1922      }
1923      $out .= ">\n";
1924
1925      foreach($objects as $obj) {
1926        $out .= $obj->render();
1927      }
1928
1929      $out .= "</g>\n";
1930    }
1931
1932    return $out;
1933  }
1934}
1935
1936/*
1937 * The Path class represents lines and polygons.
1938 */
1939class SVGPath {
1940  private $options;
1941  private $points;
1942  private $ticks;
1943  private $flags;
1944  private $text;
1945  private $name;
1946
1947  private static $id = 0;
1948
1949  const CLOSED = 0x1;
1950
1951  public function __construct() {
1952    $this->options = array();
1953    $this->points = array();
1954    $this->text = array();
1955    $this->ticks = array();
1956    $this->flags = 0;
1957    $this->name = self::$id++;
1958  }
1959
1960  /*
1961   * Making sure that we always started at the top left coordinate
1962   * makes so many things so much easier. First, find the lowest Y
1963   * position. Then, of all matching Y positions, find the lowest X
1964   * position. This is the top left.
1965   *
1966   * As far as the points are considered, they're definitely on the
1967   * top somewhere, but not necessarily the most left. This could
1968   * happen if there was a corner connector in the top edge (perhaps
1969   * for a line to connect to). Since we couldn't turn right there,
1970   * we have to try now.
1971   *
1972   * This should only be called when we close a polygon.
1973   */
1974  public function orderPoints() {
1975    $pPoints = count($this->points);
1976
1977    $minY = $this->points[0]->y;
1978    $minX = $this->points[0]->x;
1979    $minIdx = 0;
1980    for ($i = 1; $i < $pPoints; $i++) {
1981      if ($this->points[$i]->y <= $minY) {
1982        $minY = $this->points[$i]->y;
1983
1984        if ($this->points[$i]->x < $minX) {
1985          $minX = $this->points[$i]->x;
1986          $minIdx = $i;
1987        }
1988      }
1989    }
1990
1991    /*
1992     * If our top left isn't at the 0th index, it is at the end. If
1993     * there are bits after it, we need to cut those and put them at
1994     * the front.
1995     */
1996    if ($minIdx != 0) {
1997      $startPoints = array_splice($this->points, $minIdx);
1998      $this->points = array_merge($startPoints, $this->points);
1999    }
2000  }
2001
2002  /*
2003   * Useful for recursive walkers when speculatively trying a direction.
2004   */
2005  public function popPoint() {
2006    array_pop($this->points);
2007  }
2008
2009  public function addPoint($x, $y, $flags = Point::POINT) {
2010    $p = new Point($x, $y);
2011
2012    /*
2013     * If we attempt to add our original point back to the path, the polygon
2014     * must be closed.
2015     */
2016    if (count($this->points) > 0) {
2017      if ($this->points[0]->x == $p->x && $this->points[0]->y == $p->y) {
2018        $this->flags |= self::CLOSED;
2019        return true;
2020      }
2021
2022      /*
2023      * For the purposes of this library, paths should never intersect each
2024      * other. Even in the case of closing the polygon, we do not store the
2025      * final coordinate twice.
2026      */
2027      foreach ($this->points as $point) {
2028        if ($point->x == $p->x && $point->y == $p->y) {
2029          return true;
2030        }
2031      }
2032    }
2033
2034    $p->flags |= $flags;
2035    $this->points[] = $p;
2036
2037    return false;
2038  }
2039
2040  /*
2041   * It's useful to be able to know the points in a shape.
2042   */
2043  public function getPoints() {
2044    return $this->points;
2045  }
2046
2047  /*
2048   * Add a marker to a line. The third argument specifies which marker to use,
2049   * and this depends on the orientation of the line. Due to the way the line
2050   * parser works, we may have to use an inverted representation.
2051   */
2052  public function addMarker($x, $y, $t) {
2053    $p = new Point($x, $y);
2054    $p->flags |= $t;
2055    $this->points[] = $p;
2056  }
2057
2058  public function addTick($x, $y, $t) {
2059    $p = new Point($x, $y);
2060    $p->flags |= $t;
2061    $this->ticks[] = $p;
2062  }
2063
2064  /*
2065   * Is this path closed?
2066   */
2067  public function isClosed() {
2068    return ($this->flags & self::CLOSED);
2069  }
2070
2071  public function addText($t) {
2072    $this->text[] = $t;
2073  }
2074
2075  public function getText() {
2076    return $this->text;
2077  }
2078
2079  public function getID() {
2080    return $this->name;
2081  }
2082
2083  /*
2084   * Set options as a JSON string. Specified as a merge operation so that it
2085   * can be called after an individual setOption call.
2086   */
2087  public function setOptions($opt) {
2088    $this->options = array_merge($this->options, $opt);
2089  }
2090
2091  public function setOption($opt, $val) {
2092    $this->options[$opt] = $val;
2093  }
2094
2095  public function getOption($opt) {
2096    if (isset($this->options[$opt])) {
2097      return $this->options[$opt];
2098    }
2099
2100    return null;
2101  }
2102
2103  /*
2104   * Does the given point exist within this polygon? Since we can
2105   * theoretically have some complex concave and convex polygon edges in the
2106   * same shape, we need to do a full point-in-polygon test. This algorithm
2107   * seems like the standard one. See: http://alienryderflex.com/polygon/
2108   */
2109  public function hasPoint($x, $y) {
2110    if ($this->isClosed() == false) {
2111      return false;
2112    }
2113
2114    $oddNodes = false;
2115
2116    $bound = count($this->points);
2117    for ($i = 0, $j = count($this->points) - 1; $i < $bound; $i++) {
2118      if (($this->points[$i]->gridY < $y && $this->points[$j]->gridY >= $y ||
2119           $this->points[$j]->gridY < $y && $this->points[$i]->gridY >= $y) &&
2120          ($this->points[$i]->gridX <= $x || $this->points[$j]->gridX <= $x)) {
2121        if ($this->points[$i]->gridX + ($y - $this->points[$i]->gridY) /
2122            ($this->points[$j]->gridY - $this->points[$i]->gridY) *
2123            ($this->points[$j]->gridX - $this->points[$i]->gridX) < $x) {
2124          $oddNodes = !$oddNodes;
2125        }
2126      }
2127
2128      $j = $i;
2129    }
2130
2131    return $oddNodes;
2132  }
2133
2134  /*
2135   * Apply a matrix transformation to the coordinates ($x, $y). The
2136   * multiplication is implemented on the matrices:
2137   *
2138   * | a b c |   | x |
2139   * | d e f | * | y |
2140   * | 0 0 1 |   | 1 |
2141   *
2142   * Additional information on the transformations and what each R,C in the
2143   * transformation matrix represents, see:
2144   *
2145   * http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined
2146   */
2147  private function matrixTransform($matrix, $x, $y) {
2148    $xyMat = array(array($x), array($y), array(1));
2149    $newXY = array(array());
2150
2151    for ($i = 0; $i < 3; $i++) {
2152      for ($j = 0; $j < 1; $j++) {
2153        $sum = 0;
2154
2155        for ($k = 0; $k < 3; $k++) {
2156          $sum += $matrix[$i][$k] * $xyMat[$k][$j];
2157        }
2158
2159        $newXY[$i][$j] = $sum;
2160      }
2161    }
2162
2163    /* Return the coordinates as a vector */
2164    return array($newXY[0][0], $newXY[1][0], $newXY[2][0]);
2165  }
2166
2167  /*
2168   * Translate the X and Y coordinates. tX and tY specify the distance to
2169   * transform.
2170   */
2171  private function translateTransform($tX, $tY, $x, $y) {
2172    $matrix = array(array(1, 0, $tX), array(0, 1, $tY), array(0, 0, 1));
2173    return $this->matrixTransform($matrix, $x, $y);
2174  }
2175
2176  /*
2177   * Scale transformations are implemented by applying the scale to the X and
2178   * Y coordinates. One unit in the new coordinate system equals $s[XY] units
2179   * in the old system. Thus, if you want to double the size of an object on
2180   * both axes, you sould call scaleTransform(0.5, 0.5, $x, $y)
2181   */
2182  private function scaleTransform($sX, $sY, $x, $y) {
2183    $matrix = array(array($sX, 0, 0), array(0, $sY, 0), array(0, 0, 1));
2184    return $this->matrixTransform($matrix, $x, $y);
2185  }
2186
2187  /*
2188   * Rotate the coordinates around the center point cX and cY. If these
2189   * are not specified, the coordinate is rotated around 0,0. The angle
2190   * is specified in degrees.
2191   */
2192  private function rotateTransform($angle, $x, $y, $cX = 0, $cY = 0) {
2193    $angle = $angle * (pi() / 180);
2194    if ($cX != 0 || $cY != 0) {
2195      list ($x, $y) = $this->translateTransform($cX, $cY, $x, $y);
2196    }
2197
2198    $matrix = array(array(cos($angle), -sin($angle), 0),
2199                    array(sin($angle), cos($angle), 0),
2200                    array(0, 0, 1));
2201    $ret = $this->matrixTransform($matrix, $x, $y);
2202
2203    if ($cX != 0 || $cY != 0) {
2204      list ($x, $y) = $this->translateTransform(-$cX, -$cY, $ret[0], $ret[1]);
2205      $ret[0] = $x;
2206      $ret[1] = $y;
2207    }
2208
2209    return $ret;
2210  }
2211
2212  /*
2213   * Skews along the X axis at specified angle. The angle is specified in
2214   * degrees.
2215   */
2216  private function skewXTransform($angle, $x, $y) {
2217    $angle = $angle * (pi() / 180);
2218    $matrix = array(array(1, tan($angle), 0), array(0, 1, 0), array(0, 0, 1));
2219    return $this->matrixTransform($matrix, $x, $y);
2220  }
2221
2222  /*
2223   * Skews along the Y axis at specified angle. The angle is specified in
2224   * degrees.
2225   */
2226  private function skewYTransform($angle, $x, $y) {
2227    $angle = $angle * (pi() / 180);
2228    $matrix = array(array(1, 0, 0), array(tan($angle), 1, 0), array(0, 0, 1));
2229    return $this->matrixTransform($matrix, $x, $y);
2230  }
2231
2232  /*
2233   * Apply a transformation to a point $p.
2234   */
2235  private function applyTransformToPoint($txf, $p, $args) {
2236    switch ($txf) {
2237    case 'translate':
2238      return $this->translateTransform($args[0], $args[1], $p->x, $p->y);
2239
2240    case 'scale':
2241      return $this->scaleTransform($args[0], $args[1], $p->x, $p->y);
2242
2243    case 'rotate':
2244      if (count($args) > 1) {
2245        return  $this->rotateTransform($args[0], $p->x, $p->y, $args[1], $args[2]);
2246      } else {
2247        return  $this->rotateTransform($args[0], $p->x, $p->y);
2248      }
2249
2250    case 'skewX':
2251      return $this->skewXTransform($args[0], $p->x, $p->y);
2252
2253    case 'skewY':
2254      return $this->skewYTransform($args[0], $p->x, $p->y);
2255    }
2256  }
2257
2258  /*
2259   * Apply the transformation function $txf to all coordinates on path $p
2260   * providing $args as arguments to the transformation function.
2261   */
2262  private function applyTransformToPath($txf, &$p, $args) {
2263    $pathCmds = count($p['path']);
2264    $curPoint = new Point(0, 0);
2265    $prevType = null;
2266    $curType = null;
2267
2268    for ($i = 0; $i < $pathCmds; $i++) {
2269      $cmd = &$p['path'][$i];
2270
2271      $prevType = $curType;
2272      $curType = $cmd[0];
2273
2274      switch ($curType) {
2275      case 'z':
2276      case 'Z':
2277        /* Can't transform this */
2278        break;
2279
2280      case 'm':
2281        if ($prevType != null) {
2282          $curPoint->x += $cmd[1];
2283          $curPoint->y += $cmd[2];
2284
2285          list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2286          $curPoint->x = $x;
2287          $curPoint->y = $y;
2288
2289          $cmd[1] = $x;
2290          $cmd[2] = $y;
2291        } else {
2292          $curPoint->x = $cmd[1];
2293          $curPoint->y = $cmd[2];
2294
2295          list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2296          $curPoint->x = $x;
2297          $curPoint->y = $y;
2298
2299          $cmd[1] = $x;
2300          $cmd[2] = $y;
2301          $curType = 'l';
2302        }
2303
2304        break;
2305
2306      case 'M':
2307        $curPoint->x = $cmd[1];
2308        $curPoint->y = $cmd[2];
2309
2310        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2311        $curPoint->x = $x;
2312        $curPoint->y = $y;
2313
2314        $cmd[1] = $x;
2315        $cmd[2] = $y;
2316
2317        if ($prevType == null) {
2318          $curType = 'L';
2319        }
2320        break;
2321
2322      case 'l':
2323        $curPoint->x += $cmd[1];
2324        $curPoint->y += $cmd[2];
2325
2326        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2327        $curPoint->x = $x;
2328        $curPoint->y = $y;
2329
2330        $cmd[1] = $x;
2331        $cmd[2] = $y;
2332
2333        break;
2334
2335      case 'L':
2336        $curPoint->x = $cmd[1];
2337        $curPoint->y = $cmd[2];
2338
2339        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2340        $curPoint->x = $x;
2341        $curPoint->y = $y;
2342
2343        $cmd[1] = $x;
2344        $cmd[2] = $y;
2345
2346        break;
2347
2348      case 'v':
2349        $curPoint->y += $cmd[1];
2350        $curPoint->x += 0;
2351
2352        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2353        $curPoint->x = $x;
2354        $curPoint->y = $y;
2355
2356        $cmd[1] = $y;
2357
2358        break;
2359
2360      case 'V':
2361        $curPoint->y = $cmd[1];
2362
2363        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2364        $curPoint->x = $x;
2365        $curPoint->y = $y;
2366
2367        $cmd[1] = $y;
2368
2369        break;
2370
2371      case 'h':
2372        $curPoint->x += $cmd[1];
2373
2374        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2375        $curPoint->x = $x;
2376        $curPoint->y = $y;
2377
2378        $cmd[1] = $x;
2379
2380        break;
2381
2382      case 'H':
2383        $curPoint->x = $cmd[1];
2384
2385        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2386        $curPoint->x = $x;
2387        $curPoint->y = $y;
2388
2389        $cmd[1] = $x;
2390
2391        break;
2392
2393      case 'c':
2394        $tP = new Point(0, 0);
2395        $tP->x = $curPoint->x + $cmd[1]; $tP->y = $curPoint->y + $cmd[2];
2396        list ($x, $y) = $this->applyTransformToPoint($txf, $tP, $args);
2397        $cmd[1] = $x;
2398        $cmd[2] = $y;
2399
2400        $tP->x = $curPoint->x + $cmd[3]; $tP->y = $curPoint->y + $cmd[4];
2401        list ($x, $y) = $this->applyTransformToPoint($txf, $tP, $args);
2402        $cmd[3] = $x;
2403        $cmd[4] = $y;
2404
2405        $curPoint->x += $cmd[5];
2406        $curPoint->y += $cmd[6];
2407        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2408
2409        $curPoint->x = $x;
2410        $curPoint->y = $y;
2411        $cmd[5] = $x;
2412        $cmd[6] = $y;
2413
2414        break;
2415      case 'C':
2416        $curPoint->x = $cmd[1];
2417        $curPoint->y = $cmd[2];
2418        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2419        $cmd[1] = $x;
2420        $cmd[2] = $y;
2421
2422        $curPoint->x = $cmd[3];
2423        $curPoint->y = $cmd[4];
2424        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2425        $cmd[3] = $x;
2426        $cmd[4] = $y;
2427
2428        $curPoint->x = $cmd[5];
2429        $curPoint->y = $cmd[6];
2430        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2431
2432        $curPoint->x = $x;
2433        $curPoint->y = $y;
2434        $cmd[5] = $x;
2435        $cmd[6] = $y;
2436
2437        break;
2438
2439      case 's':
2440      case 'S':
2441
2442      case 'q':
2443      case 'Q':
2444
2445      case 't':
2446      case 'T':
2447
2448      case 'a':
2449        break;
2450
2451      case 'A':
2452        /*
2453         * This radius is relative to the start and end points, so it makes
2454         * sense to scale, rotate, or skew it, but not translate it.
2455         */
2456        if ($txf != 'translate') {
2457          $curPoint->x = $cmd[1];
2458          $curPoint->y = $cmd[2];
2459          list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2460          $cmd[1] = $x;
2461          $cmd[2] = $y;
2462        }
2463
2464        $curPoint->x = $cmd[6];
2465        $curPoint->y = $cmd[7];
2466        list ($x, $y) = $this->applyTransformToPoint($txf, $curPoint, $args);
2467        $curPoint->x = $x;
2468        $curPoint->y = $y;
2469        $cmd[6] = $x;
2470        $cmd[7] = $y;
2471
2472        break;
2473      }
2474    }
2475  }
2476
2477  public function render() {
2478    $startPoint = array_shift($this->points);
2479    $endPoint = $this->points[count($this->points) - 1];
2480
2481    $out = "<g id=\"group{$this->name}\">\n";
2482
2483    /*
2484     * If someone has specified one of our special object types, we are going
2485     * to want to completely override any of the pathing that we would have
2486     * done otherwise, but we defer until here to do anything about it because
2487     * we need information about the object we're replacing.
2488     */
2489    if (isset($this->options['a2s:type']) &&
2490        isset(CustomObjects::$objects[$this->options['a2s:type']])) {
2491      $object = CustomObjects::$objects[$this->options['a2s:type']];
2492
2493      /* Again, if no fill was specified, specify one. */
2494      if (!isset($this->options['fill'])) {
2495        $this->options['fill'] = '#fff';
2496      }
2497
2498      /*
2499       * We don't care so much about the area, but we do care about the width
2500       * and height of the object. All of our "custom" objects are implemented
2501       * in 100x100 space, which makes the transformation marginally easier.
2502       */
2503      $minX = $startPoint->x; $maxX = $minX;
2504      $minY = $startPoint->y; $maxY = $minY;
2505      foreach ($this->points as $p) {
2506        if ($p->x < $minX) {
2507          $minX = $p->x;
2508        } elseif ($p->x > $maxX) {
2509          $maxX = $p->x;
2510        }
2511        if ($p->y < $minY) {
2512          $minY = $p->y;
2513        } elseif ($p->y > $maxY) {
2514          $maxY = $p->y;
2515        }
2516      }
2517
2518      $objW = $maxX - $minX;
2519      $objH = $maxY - $minY;
2520
2521      if (isset($this->options['a2s:link'])) {
2522        $out .="<a xlink:href=\"".$this->options['a2s:link']."\">";
2523      }
2524
2525      $i = 0;
2526      foreach ($object as $o) {
2527        $id = self::$id++;
2528        $out .= "\t<path id=\"path{$this->name}\" d=\"";
2529
2530        $oW = $o['width'];
2531        $oH = $o['height'];
2532
2533        $this->applyTransformToPath('scale', $o, array($objW/$oW, $objH/$oH));
2534        $this->applyTransformToPath('translate', $o, array($minX, $minY));
2535
2536        foreach ($o['path'] as $cmd) {
2537          $out .= join(' ', $cmd) . ' ';
2538        }
2539        $out .= '" ';
2540
2541        /* Don't add options to sub-paths */
2542        if ($i++ < 1) {
2543          foreach ($this->options as $opt => $val) {
2544            if (strpos($opt, 'a2s:', 0) === 0) {
2545              continue;
2546            }
2547            $out .= "$opt=\"$val\" ";
2548          }
2549        }
2550
2551        $out .= " />\n";
2552      }
2553
2554      if (count($this->text) > 0) {
2555        foreach ($this->text as $text) {
2556          $out .= "\t" . $text->render() . "\n";
2557        }
2558      }
2559
2560      if (isset($this->options['a2s:link'])) {
2561        $out .="</a>";
2562      }
2563
2564      $out .= "</g>\n";
2565
2566      /* Bazinga. */
2567      return $out;
2568    }
2569
2570    /*
2571     * Nothing fancy here -- this is just rendering for our standard
2572     * polygons.
2573     *
2574     * Our start point is represented by a single moveto command (unless the
2575     * start point is curved) as the shape will be closed with the Z command
2576     * automatically if it is a closed shape. If we have a control point, we
2577     * have to go ahead and draw the curve.
2578     */
2579    if (($startPoint->flags & Point::CONTROL)) {
2580      $cX = $startPoint->x;
2581      $cY = $startPoint->y;
2582      $sX = $startPoint->x;
2583      $sY = $startPoint->y + 10;
2584      $eX = $startPoint->x + 10;
2585      $eY = $startPoint->y;
2586
2587      $path = "M {$sX} {$sY} Q {$cX} {$cY} {$eX} {$eY} ";
2588    } else {
2589      $path = "M {$startPoint->x} {$startPoint->y} ";
2590    }
2591
2592    $prevP = $startPoint;
2593    $bound = count($this->points);
2594    for ($i = 0; $i < $bound; $i++) {
2595      $p = $this->points[$i];
2596
2597      /*
2598       * Handle quadratic Bezier curves. NOTE: This algorithm for drawing
2599       * the curves only works if the shapes are drawn in a clockwise
2600       * manner.
2601       */
2602      if (($p->flags & Point::CONTROL)) {
2603        /* Our control point is always the original corner */
2604        $cX = $p->x;
2605        $cY = $p->y;
2606
2607        /* Need next point to determine which way to turn */
2608        if ($i == count($this->points) - 1) {
2609          $nP = $startPoint;
2610        } else {
2611          $nP = $this->points[$i + 1];
2612        }
2613
2614        if ($prevP->x == $p->x) {
2615          /*
2616           * If we are on the same vertical axis, our starting X coordinate
2617           * is the same as the control point coordinate.
2618           */
2619          $sX = $p->x;
2620
2621          /* Offset start point from control point in the proper direction */
2622          if ($prevP->y < $p->y) {
2623            $sY = $p->y - 10;
2624          } else {
2625            $sY = $p->y + 10;
2626          }
2627
2628          $eY = $p->y;
2629          /* Offset end point from control point in the proper direction */
2630          if ($nP->x < $p->x) {
2631            $eX = $p->x - 10;
2632          } else {
2633            $eX = $p->x + 10;
2634          }
2635        } elseif ($prevP->y == $p->y) {
2636          /* Horizontal decisions mirror vertical's above */
2637          $sY = $p->y;
2638          if ($prevP->x < $p->x) {
2639            $sX = $p->x - 10;
2640          } else {
2641            $sX = $p->x + 10;
2642          }
2643
2644          $eX = $p->x;
2645          if ($nP->y <= $p->y) {
2646            $eY = $p->y - 10;
2647          } else {
2648            $eY = $p->y + 10;
2649          }
2650        }
2651
2652        $path .= "L {$sX} {$sY} Q {$cX} {$cY} {$eX} {$eY} ";
2653      } else {
2654        /* The excruciating difficulty of drawing a straight line */
2655        $path .= "L {$p->x} {$p->y} ";
2656      }
2657
2658      $prevP = $p;
2659    }
2660
2661    if ($this->isClosed()) {
2662      $path .= 'Z';
2663    }
2664
2665    $id = self::$id++;
2666
2667    /* Add markers if necessary. */
2668    if ($startPoint->flags & Point::SMARKER) {
2669      $this->options["marker-start"] = "url(#Pointer)";
2670    } elseif ($startPoint->flags & Point::IMARKER) {
2671      $this->options["marker-start"] = "url(#iPointer)";
2672    }
2673
2674    if ($endPoint->flags & Point::SMARKER) {
2675      $this->options["marker-end"] = "url(#Pointer)";
2676    } elseif ($endPoint->flags & Point::IMARKER) {
2677      $this->options["marker-end"] = "url(#iPointer)";
2678    }
2679
2680    /*
2681     * SVG objects without a fill will be transparent, and this looks so
2682     * terrible with the drop-shadow effect. Any objects that aren't filled
2683     * automatically get a white fill.
2684     */
2685    if ($this->isClosed() && !isset($this->options['fill'])) {
2686      $this->options['fill'] = '#fff';
2687    }
2688
2689    $out_p = "\t<path id=\"path{$this->name}\" ";
2690    foreach ($this->options as $opt => $val) {
2691      if (strpos($opt, 'a2s:', 0) === 0) {
2692        if ($opt=='a2s:link') {
2693    	  $alnk = $val;
2694        }
2695    	continue;
2696      }
2697      $out_p .= "$opt=\"$val\" ";
2698    }
2699    if (isset($alnk)) {
2700      $out_p = "\t<a xlink:href=\"".$alnk."\">".$out_p;
2701    }
2702    $out_p .= "d=\"{$path}\" />\n";
2703
2704    if (count($this->text) > 0) {
2705      foreach ($this->text as $text) {
2706        $out_p .= "\t" . $text->render() . "\n";
2707      }
2708    }
2709
2710    if (isset($alnk)) {
2711      $out_p .= '</a>';
2712    }
2713    $out .= $out_p;
2714
2715    $bound = count($this->ticks);
2716    for ($i = 0; $i < $bound; $i++) {
2717      $t = $this->ticks[$i];
2718      if ($t->flags & Point::DOT) {
2719        $out .= "<circle cx=\"{$t->x}\" cy=\"{$t->y}\" r=\"3\" fill=\"black\" />";
2720      } elseif ($t->flags & Point::TICK) {
2721        $x1 = $t->x - 4;
2722        $y1 = $t->y - 4;
2723        $x2 = $t->x + 4;
2724        $y2 = $t->y + 4;
2725        $out .= "<line x1=\"$x1\" y1=\"$y1\" x2=\"$x2\" y2=\"$y2\" stroke-width=\"1\" />";
2726
2727        $x1 = $t->x + 4;
2728        $y1 = $t->y - 4;
2729        $x2 = $t->x - 4;
2730        $y2 = $t->y + 4;
2731        $out .= "<line x1=\"$x1\" y1=\"$y1\" x2=\"$x2\" y2=\"$y2\" stroke-width=\"1\" />";
2732      }
2733    }
2734
2735    $out .= "</g>\n";
2736    return $out;
2737  }
2738}
2739
2740/*
2741 * Nothing really special here. Container for representing text bits.
2742 */
2743class SVGText {
2744  private $options;
2745  private $string;
2746  private $point;
2747  private $name;
2748
2749  private static $id = 0;
2750
2751  private static function svgEntities($str) {
2752    /* <, >, and & replacements are valid without a custom DTD:
2753     * https://www.w3.org/TR/xml/#syntax
2754     *
2755     * We want to replace these in text without confusing SVG.
2756     */
2757    $s = array('&','<', '>');
2758    $r = array('&amp;', '&lt;', '&gt;');
2759    return str_replace($s, $r, $str);
2760  }
2761
2762  public function __construct($x, $y) {
2763    $this->point = new Point($x, $y);
2764    $this->name = self::$id++;
2765    $this->options = array();
2766  }
2767
2768  public function setOption($opt, $val) {
2769    $this->options[$opt] = $val;
2770  }
2771
2772  public function getID() {
2773    return $this->name;
2774  }
2775
2776  public function getPoint() {
2777    return $this->point;
2778  }
2779
2780  public function setString($string) {
2781    $this->string = $string;
2782  }
2783
2784  public function render() {
2785    $out = "<text x=\"{$this->point->x}\" y=\"{$this->point->y}\" id=\"text{$this->name}\" ";
2786    foreach ($this->options as $opt => $val) {
2787      if (strpos($opt, 'a2s:', 0) === 0) {
2788        continue;
2789      }
2790      $out .= "$opt=\"$val\" ";
2791    }
2792    $out .= ">";
2793    $out .= SVGText::svgEntities($this->string);
2794    $out .= "</text>\n";
2795    return $out;
2796  }
2797}
2798
2799/*
2800 * Main class for parsing ASCII and constructing the SVG output based on the
2801 * above classes.
2802 */
2803class ASCIIToSVG {
2804  public $blurDropShadow = true;
2805  public $fontFamily = "Consolas,Monaco,Anonymous Pro,Anonymous,Bitstream Sans Mono,monospace";
2806
2807  private $rawData;
2808  private $grid;
2809
2810  private $svgObjects;
2811  private $clearCorners;
2812
2813  /* Directions for traversing lines in our grid */
2814  const DIR_UP    = 0x1;
2815  const DIR_DOWN  = 0x2;
2816  const DIR_LEFT  = 0x4;
2817  const DIR_RIGHT = 0x8;
2818  const DIR_NE    = 0x10;
2819  const DIR_SE    = 0x20;
2820
2821  public function __construct($data) {
2822    /* For debugging purposes */
2823    $this->rawData = $data;
2824
2825    CustomObjects::loadObjects();
2826
2827    $this->clearCorners = array();
2828
2829    /*
2830     * Parse out any command references. These need to be at the bottom of the
2831     * diagram due to the way they're removed. Format is:
2832     * [identifier] optional-colon optional-spaces ({json-blob})\n
2833     *
2834     * The JSON blob may not contain objects as values or the regex will break.
2835     */
2836    $this->commands = array();
2837    preg_match_all('/^\[([^\]]+)\]:?\s+({[^}]+?})/ims', $data, $matches);
2838    $bound = count($matches[1]);
2839    for ($i = 0; $i < $bound; $i++) {
2840      $this->commands[$matches[1][$i]] = json_decode($matches[2][$i], true);
2841    }
2842
2843    $data = preg_replace('/^\[([^\]]+)\](:?)\s+.*/ims', '', $data);
2844
2845    /*
2846     * Treat our UTF-8 field as a grid and store each character as a point in
2847     * that grid. The (0, 0) coordinate on this grid is top-left, just as it
2848     * is in images.
2849     */
2850    $this->grid = explode("\n", $data);
2851
2852    foreach ($this->grid as $k => $line) {
2853      $this->grid[$k] = preg_split('//u', $line, null, PREG_SPLIT_NO_EMPTY);
2854    }
2855
2856    $this->svgObjects = new SVGGroup();
2857  }
2858
2859  /*
2860   * This is kind of a stupid and hacky way to do this, but this allows setting
2861   * the default scale of one grid space on the X and Y axes.
2862   */
2863  public function setDimensionScale($x, $y) {
2864    $o = Scale::getInstance();
2865    $o->setScale($x, $y);
2866  }
2867
2868  public function dump() {
2869    var_export($this);
2870  }
2871
2872  /* Render out what we've done!  */
2873  public function render() {
2874    $o = Scale::getInstance();
2875
2876    /* Figure out how wide we need to make the canvas */
2877    $canvasWidth = 0;
2878    foreach($this->grid as $line) {
2879      if (count($line) > $canvasWidth) {
2880        $canvasWidth = count($line);
2881      }
2882    }
2883
2884    $canvasWidth = $canvasWidth * $o->xScale + 10;
2885    $canvasHeight = count($this->grid) * $o->yScale;
2886
2887    /*
2888     * Boilerplate header with definitions that we might be using for markers
2889     * and drop shadows.
2890     */
2891/*
2892<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2893<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
2894  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
2895<!-- Created with ASCIIToSVG (https://github.com/dhobsd/asciitosvg/) -->
2896*/
2897    $out = <<<SVG
2898width="{$canvasWidth}px" height="{$canvasHeight}px"
2899viewBox="0 0 {$canvasWidth} {$canvasHeight}" version="1.1"
2900  xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2901  <!-- Created with ASCIIToSVG (https://github.com/dhobsd/asciitosvg/) -->
2902  <defs>
2903    <filter id="dsFilterNoBlur" width="150%" height="150%">
2904      <feOffset result="offOut" in="SourceGraphic" dx="3" dy="3"/>
2905      <feColorMatrix result="matrixOut" in="offOut" type="matrix" values="0.2 0 0 0 0 0 0.2 0 0 0 0 0 0.2 0 0 0 0 0 1 0"/>
2906      <feBlend in="SourceGraphic" in2="matrixOut" mode="normal"/>
2907    </filter>
2908    <filter id="dsFilter" width="150%" height="150%">
2909      <feOffset result="offOut" in="SourceGraphic" dx="3" dy="3"/>
2910      <feColorMatrix result="matrixOut" in="offOut" type="matrix" values="0.2 0 0 0 0 0 0.2 0 0 0 0 0 0.2 0 0 0 0 0 1 0"/>
2911      <feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="3"/>
2912      <feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
2913    </filter>
2914    <marker id="iPointer"
2915      viewBox="0 0 10 10" refX="5" refY="5"
2916      markerUnits="strokeWidth"
2917      markerWidth="8" markerHeight="7"
2918      fill="black"
2919      orient="auto">
2920      <path d="M 10 0 L 10 10 L 0 5 z" />
2921    </marker>
2922    <marker id="Pointer"
2923      viewBox="0 0 10 10" refX="5" refY="5"
2924      markerUnits="strokeWidth"
2925      markerWidth="8" markerHeight="7"
2926      fill="black"
2927      orient="auto">
2928      <path d="M 0 0 L 10 5 L 0 10 z" />
2929    </marker>
2930  </defs>
2931SVG;
2932
2933    /* Render the group, everything lives in there */
2934    $out .= $this->svgObjects->render();
2935
2936    $out .= "</svg>\n";
2937
2938    return $out;
2939  }
2940
2941  /*
2942   * Parsing the grid is a multi-step process. We parse out boxes first, as
2943   * this makes it easier to then parse lines. By parse out, I do mean we
2944   * parse them and then remove them. This does mean that a complete line
2945   * will not travel along the edge of a box, but you probably won't notice
2946   * unless the box is curved anyway. While edges are removed, points are
2947   * not. This means that you can cleanly allow lines to intersect boxes
2948   * (as long as they do not bisect!
2949   *
2950   * After parsing boxes and lines, we remove the corners from the grid. At
2951   * this point, all we have left should be text, which we can pick up and
2952   * place.
2953   */
2954  public function parseGrid() {
2955    $this->parseBoxes();
2956    $this->parseLines();
2957
2958    foreach ($this->clearCorners as $corner) {
2959      $this->grid[$corner[0]][$corner[1]] = ' ';
2960    }
2961
2962    $this->parseText();
2963
2964    $this->injectCommands();
2965  }
2966
2967  /*
2968   * Ahh, good ol' box parsing. We do this by scanning each row for points and
2969   * attempting to close the shape. Since the approach is first horizontal,
2970   * then vertical, we complete the shape in a clockwise order (which is
2971   * important for the Bezier curve generation.
2972   */
2973  private function parseBoxes() {
2974    /* Set up our box group  */
2975    $this->svgObjects->pushGroup('boxes');
2976    $this->svgObjects->setOption('stroke', 'black');
2977    $this->svgObjects->setOption('stroke-width', '2');
2978    $this->svgObjects->setOption('fill', 'none');
2979
2980    /* Scan the grid for corners */
2981    foreach ($this->grid as $row => $line) {
2982      foreach ($line as $col => $char) {
2983        if ($this->isCorner($char)) {
2984          $path = new SVGPath();
2985
2986          if ($char == '.' || $char == "'") {
2987            $path->addPoint($col, $row, Point::CONTROL);
2988          } else {
2989            $path->addPoint($col, $row);
2990          }
2991
2992          /*
2993           * The wall follower is a left-turning, marking follower. See that
2994           * function for more information on how it works.
2995           */
2996          $this->wallFollow($path, $row, $col+1, self::DIR_RIGHT);
2997
2998          /* We only care about closed polygons */
2999          if ($path->isClosed()) {
3000            $path->orderPoints();
3001
3002            $skip = false;
3003            /*
3004             * The walking code can find the same box from a different edge:
3005             *
3006             * +---+   +---+
3007             * |   |   |   |
3008             * |   +---+   |
3009             * +-----------+
3010             *
3011             * so ignore adding a box that we've already added.
3012             */
3013            foreach($this->svgObjects->getGroup('boxes') as $box) {
3014              $bP = $box->getPoints();
3015              $pP = $path->getPoints();
3016              $pPoints = count($pP);
3017              $shared = 0;
3018
3019              /*
3020               * If the boxes don't have the same number of edges, they
3021               * obviously cannot be the same box.
3022               */
3023              if (count($bP) != $pPoints) {
3024                continue;
3025              }
3026
3027              /* Traverse the vertices of this new box... */
3028              for ($i = 0; $i < $pPoints; $i++) {
3029                /* ...and find them in this existing box. */
3030                for ($j = 0; $j < $pPoints; $j++) {
3031                  if ($pP[$i]->x == $bP[$j]->x && $pP[$i]->y == $bP[$j]->y) {
3032                    $shared++;
3033                  }
3034                }
3035              }
3036
3037              /* If all the edges are in common, it's the same shape. */
3038              if ($shared == count($bP)) {
3039                $skip = true;
3040                break;
3041              }
3042            }
3043
3044            if ($skip == false) {
3045              /* Search for any references for styling this polygon; add it */
3046              if ($this->blurDropShadow) {
3047                $path->setOption('filter', 'url(#dsFilter)');
3048              } else {
3049                $path->setOption('filter', 'url(#dsFilterNoBlur)');
3050              }
3051
3052              $name = $this->findCommands($path);
3053
3054              $this->svgObjects->addObject($path);
3055            }
3056          }
3057        }
3058      }
3059    }
3060
3061    /*
3062     * Once we've found all the boxes, we want to remove them from the grid so
3063     * that they don't confuse the line parser. However, we don't remove any
3064     * corner characters because these might be shared by lines.
3065     */
3066    foreach ($this->svgObjects->getGroup('boxes') as $box) {
3067      $this->clearObject($box);
3068    }
3069
3070    /* Anything after this is not a subgroup */
3071    $this->svgObjects->popGroup();
3072  }
3073
3074  /*
3075   * Our line parser operates differently than the polygon parser. This is
3076   * because lines are not intrinsically marked with starting points (markers
3077   * are optional) -- they just sort of begin. Additionally, so that markers
3078   * will work, we can't just construct a line from some random point: we need
3079   * to start at the correct edge.
3080   *
3081   * Thus, the line parser traverses vertically first, then horizontally. Once
3082   * a line is found, it is cleared immediately (but leaving any control points
3083   * in case there were any intersections.
3084   */
3085  private function parseLines() {
3086    /* Set standard line options */
3087    $this->svgObjects->pushGroup('lines');
3088    $this->svgObjects->setOption('stroke', 'black');
3089    $this->svgObjects->setOption('stroke-width', '2');
3090    $this->svgObjects->setOption('fill', 'none');
3091
3092    /* The grid is not uniform, so we need to determine the longest row. */
3093    $maxCols = 0;
3094    $bound = count($this->grid);
3095    for ($r = 0; $r < $bound; $r++) {
3096      $maxCols = max($maxCols, count($this->grid[$r]));
3097    }
3098
3099    for ($c = 0; $c < $maxCols; $c++) {
3100      for ($r = 0; $r < $bound; $r++) {
3101        /* This gets set if we find a line-start here. */
3102        $dir = false;
3103
3104        $line = new SVGPath();
3105
3106        /*
3107         * Since the column count isn't uniform, don't attempt to handle any
3108         * rows that don't extend out this far.
3109         */
3110        if (!isset($this->grid[$r][$c])) {
3111          continue;
3112        }
3113
3114        $char = $this->getChar($r, $c);
3115        switch ($char) {
3116        /*
3117         * Do marker characters first. These are the easiest because they are
3118         * basically guaranteed to represent the start of the line.
3119         */
3120        case '<':
3121          $e = $this->getChar($r, $c + 1);
3122          if ($this->isEdge($e, self::DIR_RIGHT) || $this->isCorner($e)) {
3123            $line->addMarker($c, $r, Point::IMARKER);
3124            $dir = self::DIR_RIGHT;
3125          } else {
3126            $se = $this->getChar($r + 1, $c + 1);
3127            $ne = $this->getChar($r - 1, $c + 1);
3128            if ($se == "\\") {
3129              $line->addMarker($c, $r, Point::IMARKER);
3130              $dir = self::DIR_SE;
3131            } elseif ($ne == '/') {
3132              $line->addMarker($c, $r, Point::IMARKER);
3133              $dir = self::DIR_NE;
3134            }
3135          }
3136          break;
3137        case '^':
3138          $s = $this->getChar($r + 1, $c);
3139          if ($this->isEdge($s, self::DIR_DOWN) || $this->isCorner($s)) {
3140            $line->addMarker($c, $r, Point::IMARKER);
3141            $dir = self::DIR_DOWN;
3142          } elseif ($this->getChar($r + 1, $c + 1) == "\\") {
3143            /* Don't need to check west for diagonals. */
3144            $line->addMarker($c, $r, Point::IMARKER);
3145            $dir = self::DIR_SE;
3146          }
3147          break;
3148        case '>':
3149          $w = $this->getChar($r, $c - 1);
3150          if ($this->isEdge($w, self::DIR_LEFT) || $this->isCorner($w)) {
3151            $line->addMarker($c, $r, Point::IMARKER);
3152            $dir = self::DIR_LEFT;
3153          }
3154          /* All diagonals come from west, so we don't need to check */
3155          break;
3156        case 'v':
3157          $n = $this->getChar($r - 1, $c);
3158          if ($this->isEdge($n, self::DIR_UP) || $this->isCorner($n)) {
3159            $line->addMarker($c, $r, Point::IMARKER);
3160            $dir = self::DIR_UP;
3161          } elseif ($this->getChar($r - 1, $c + 1) == '/') {
3162            $line->addMarker($c, $r, Point::IMARKER);
3163            $dir = self::DIR_NE;
3164          }
3165          break;
3166
3167        /*
3168         * Edges are handled specially. We have to look at the context of the
3169         * edge to determine whether it's the start of a line. A vertical edge
3170         * can appear as the start of a line in the following circumstances:
3171         *
3172         * +-------------      +--------------     +----    | (s)
3173         * |                   |                   |        |
3174         * |      | (s)        +-------+           |(s)     |
3175         * +------+                    | (s)
3176         *
3177         * From this we can extrapolate that we are a starting edge if our
3178         * southern neighbor is a vertical edge or corner, but we have no line
3179         * material to our north (and vice versa). This logic does allow for
3180         * the southern / northern neighbor to be part of a separate
3181         * horizontal line.
3182         */
3183        case ':':
3184          $line->setOption('stroke-dasharray', '5 5');
3185          /* FALLTHROUGH */
3186        case '|':
3187          $n = $this->getChar($r-1, $c);
3188          $s = $this->getChar($r+1, $c);
3189          if (($s == '|' || $s == ':' || $this->isCorner($s)) &&
3190              $n != '|' && $n != ':' && !$this->isCorner($n) &&
3191              $n != '^') {
3192            $dir = self::DIR_DOWN;
3193          } elseif (($n == '|' || $n == ':' || $this->isCorner($n)) &&
3194                    $s != '|' && $s != ':' && !$this->isCorner($s) &&
3195                    $s != 'v') {
3196            $dir = self::DIR_UP;
3197          }
3198          break;
3199
3200        /*
3201         * Horizontal edges have the same properties for search as vertical
3202         * edges, except we need to look east / west. The diagrams for the
3203         * vertical case are still accurate to visualize this case; just
3204         * mentally turn them 90 degrees clockwise.
3205         */
3206        case '=':
3207          $line->setOption('stroke-dasharray', '5 5');
3208          /* FALLTHROUGH */
3209        case '-':
3210          $w = $this->getChar($r, $c-1);
3211          $e = $this->getChar($r, $c+1);
3212          if (($w == '-' || $w == '=' || $this->isCorner($w)) &&
3213              $e != '=' && $e != '-' && !$this->isCorner($e) &&
3214              $e != '>') {
3215            $dir = self::DIR_LEFT;
3216          } elseif (($e == '-' || $e == '=' || $this->isCorner($e)) &&
3217                    $w != '=' && $w != '-' && !$this->isCorner($w) &&
3218                    $w != '<') {
3219            $dir = self::DIR_RIGHT;
3220          }
3221          break;
3222
3223        /*
3224         * We can only find diagonals going north or south and east. This is
3225         * simplified due to the fact that they have no corners. We are
3226         * guaranteed to run into their westernmost point or their relevant
3227         * marker.
3228         */
3229        case '/':
3230          $ne = $this->getChar($r-1, $c+1);
3231          if ($ne == '/' || $ne == '^' || $ne == '>') {
3232            $dir = self::DIR_NE;
3233          }
3234          break;
3235
3236        case "\\":
3237          $se =  $this->getChar($r+1, $c+1);
3238          if ($se == "\\" || $se == "v" || $se == '>') {
3239            $dir = self::DIR_SE;
3240          }
3241          break;
3242
3243        /*
3244         * The corner case must consider all four directions. Though a
3245         * reasonable person wouldn't use slant corners for this, they are
3246         * considered corners, so it kind of makes sense to handle them the
3247         * same way. For this case, envision the starting point being a corner
3248         * character in both the horizontal and vertical case. And then
3249         * mentally overlay them and consider that :).
3250         */
3251        case '+':
3252        case '#':
3253          $ne = $this->getChar($r-1, $c+1);
3254          $se =  $this->getChar($r+1, $c+1);
3255          if ($ne == '/' || $ne == '^' || $ne == '>') {
3256            $dir = self::DIR_NE;
3257          } elseif ($se == "\\" || $se == "v" || $se == '>') {
3258            $dir = self::DIR_SE;
3259          }
3260          /* FALLTHROUGH */
3261
3262        case '.':
3263        case "'":
3264          $n = $this->getChar($r-1, $c);
3265          $w = $this->getChar($r, $c-1);
3266          $s = $this->getChar($r+1, $c);
3267          $e = $this->getChar($r, $c+1);
3268          if (($w == '=' || $w == '-') && $n != '|' && $n != ':' && $w != '-' &&
3269              $e != '=' && $e != '|' && $s != ':') {
3270            $dir = self::DIR_LEFT;
3271          } elseif (($e == '=' || $e == '-') && $n != '|' && $n != ':' &&
3272              $w != '-' && $w != '=' && $s != '|' && $s != ':') {
3273            $dir = self::DIR_RIGHT;
3274          } elseif (($s == '|' || $s == ':') && $n != '|' && $n != ':' &&
3275                    $w != '-' && $w != '=' && $e != '-' && $e != '=' &&
3276                    (($char != '.' && $char != "'") ||
3277                     ($char == '.' && $s != '.') ||
3278                     ($char == "'" && $s != "'"))) {
3279            $dir = self::DIR_DOWN;
3280          } elseif (($n == '|' || $n == ':') && $s != '|' && $s != ':' &&
3281                    $w != '-' && $w != '=' && $e != '-' && $e != '=' &&
3282                    (($char != '.' && $char != "'") ||
3283                     ($char == '.' && $s != '.') ||
3284                     ($char == "'" && $s != "'"))) {
3285            $dir = self::DIR_UP;
3286          }
3287          break;
3288        }
3289
3290        /* It does actually save lines! */
3291        if ($dir !== false) {
3292          $rInc = 0; $cInc = 0;
3293          if (!$this->isMarker($char)) {
3294            $line->addPoint($c, $r);
3295          }
3296
3297          /*
3298           * The walk routine may attempt to add the point again, so skip it.
3299           * If we don't, we can miss the line or end up with just a point.
3300           */
3301          if ($dir == self::DIR_UP) {
3302            $rInc = -1; $cInc = 0;
3303          } elseif ($dir == self::DIR_DOWN) {
3304            $rInc = 1; $cInc = 0;
3305          } elseif ($dir == self::DIR_RIGHT) {
3306            $rInc = 0; $cInc = 1;
3307          } elseif ($dir == self::DIR_LEFT) {
3308            $rInc = 0; $cInc = -1;
3309          } elseif ($dir == self::DIR_NE) {
3310            $rInc = -1; $cInc = 1;
3311          } elseif ($dir == self::DIR_SE) {
3312            $rInc = 1; $cInc = 1;
3313          }
3314
3315          /*
3316           * Walk the points of this line. Note we don't use wallFollow; we are
3317           * operating under the assumption that lines do not meander. (And, in
3318           * any event, that algorithm is intended to find a closed object.)
3319           */
3320          $this->walk($line, $r+$rInc, $c+$cInc, $dir);
3321
3322          /*
3323           * Remove it so that we don't confuse any other lines. This leaves
3324           * corners in tact, still.
3325           */
3326          $this->clearObject($line);
3327          $this->svgObjects->addObject($line);
3328
3329          /* We may be able to find more lines starting from this same point */
3330          if ($this->isCorner($char)) {
3331            $r--;
3332          }
3333        }
3334      }
3335    }
3336
3337    $this->svgObjects->popGroup();
3338  }
3339
3340  /*
3341   * Look for text in a file. If the text appears in a box that has a dark
3342   * fill, we want to give it a light fill (and vice versa). This means we
3343   * have to figure out what box it lives in (if any) and do all sorts of
3344   * color calculation magic.
3345   */
3346  private function parseText() {
3347    $o = Scale::getInstance();
3348
3349    /*
3350     * The style options deserve some comments. The monospace and font-size
3351     * choices are not accidental. This gives the best sort of estimation
3352     * for font size to scale that I could come up with empirically.
3353     *
3354     * N.B. This might change with different scales. I kind of feel like this
3355     * is a bug waiting to be filed, but whatever.
3356     */
3357    $fSize = 0.95*$o->yScale;
3358    $this->svgObjects->pushGroup('text');
3359    $this->svgObjects->setOption('fill', 'black');
3360    $this->svgObjects->setOption('style',
3361        "font-family:{$this->fontFamily};font-size:{$fSize}px");
3362
3363    /*
3364     * Text gets the same scanning treatment as boxes. We do left-to-right
3365     * scanning, which should probably be configurable in case someone wants
3366     * to use this with e.g. Arabic or some other right-to-left language.
3367     * Either way, this isn't UTF-8 safe (thanks, PHP!!!), so that'll require
3368     * thought regardless.
3369     */
3370    $boxes = $this->svgObjects->getGroup('boxes');
3371    $bound = count($boxes);
3372
3373    foreach ($this->grid as $row => $line) {
3374      $cols = count($line);
3375      for ($i = 0; $i < $cols; $i++) {
3376        if ($this->getChar($row, $i) != ' ') {
3377          /* More magic numbers that probably need research. */
3378          $t = new SVGText($i - .6, $row + 0.3);
3379
3380          /* Time to figure out which (if any) box we live inside */
3381          $tP = $t->getPoint();
3382
3383          $maxPoint = new Point(-1, -1);
3384          $boxQueue = array();
3385
3386          for ($j = 0; $j < $bound; $j++) {
3387            if ($boxes[$j]->hasPoint($tP->gridX, $tP->gridY)) {
3388              $boxPoints = $boxes[$j]->getPoints();
3389              $boxTL = $boxPoints[0];
3390
3391              /*
3392               * This text is in this box, but it may still be in a more
3393               * specific nested box. Find the box with the highest top
3394               * left X,Y coordinate. Keep a queue of boxes in case the top
3395               * most box doesn't have a fill.
3396               */
3397              if ($boxTL->y > $maxPoint->y && $boxTL->x > $maxPoint->x) {
3398                $maxPoint->x = $boxTL->x;
3399                $maxPoint->y = $boxTL->y;
3400                $boxQueue[] = $boxes[$j];
3401              }
3402            }
3403          }
3404
3405          if (count($boxQueue) > 0) {
3406            /*
3407             * Work backwards through the boxes to find the box with the most
3408             * specific fill.
3409             */
3410            for ($j = count($boxQueue) - 1; $j >= 0; $j--) {
3411              $fill = $boxQueue[$j]->getOption('fill');
3412
3413              if ($fill == 'none' || $fill == null) {
3414                continue;
3415              }
3416
3417              if (substr($fill, 0, 1) != '#') {
3418                if (!isset($GLOBALS['A2S_colors'][strtolower($fill)])) {
3419                  continue;
3420                } else {
3421                  $fill = $GLOBALS['A2S_colors'][strtolower($fill)];
3422                }
3423              } else {
3424                if (strlen($fill) != 4 && strlen($fill) != 7) {
3425                  continue;
3426                }
3427              }
3428
3429
3430              if ($fill) {
3431                /* Attempt to parse the fill color */
3432                if (strlen($fill) == 4) {
3433                  $cR = hexdec(str_repeat($fill[1], 2));
3434                  $cG = hexdec(str_repeat($fill[2], 2));
3435                  $cB = hexdec(str_repeat($fill[3], 2));
3436                } elseif (strlen($fill) == 7) {
3437                  $cR = hexdec(substr($fill, 1, 2));
3438                  $cG = hexdec(substr($fill, 3, 2));
3439                  $cB = hexdec(substr($fill, 5, 2));
3440                }
3441
3442                /*
3443                 * This magic is gleaned from the working group paper on
3444                 * accessibility at http://www.w3.org/TR/AERT. The recommended
3445                 * contrast is a brightness difference of at least 125 and a
3446                 * color difference of at least 500. Since our default color
3447                 * is black, that makes the color difference easier.
3448                 */
3449                $bFill = (($cR * 299) + ($cG * 587) + ($cB * 114)) / 1000;
3450                $bDiff = $cR + $cG + $cB;
3451                $bText = 0;
3452
3453                if ($bFill - $bText < 125 || $bDiff < 500) {
3454                  /* If black is too dark, white will work */
3455                  $t->setOption('fill', '#fff');
3456                } else {
3457                  $t->setOption('fill', '#000');
3458                }
3459
3460                break;
3461              }
3462            }
3463
3464            if ($j < 0) {
3465              $t->setOption('fill', '#000');
3466            }
3467          } else {
3468            /* This text isn't inside a box; make it black */
3469            $t->setOption('fill', '#000');
3470          }
3471
3472          /* We found a stringy character, eat it and the rest. */
3473          $str = $this->getChar($row, $i++);
3474          while ($i < count($line) && $this->getChar($row, $i) != ' ') {
3475            $str .= $this->getChar($row, $i++);
3476            /* Eat up to 1 space */
3477            if ($this->getChar($row, $i) == ' ') {
3478              $str .= ' ';
3479              $i++;
3480            }
3481          }
3482
3483          if ($str == '') {
3484            continue;
3485          }
3486
3487          $t->setString($str);
3488
3489          /*
3490           * If we were in a box, group with the box. Otherwise it gets its
3491           * own group.
3492           */
3493          if (count($boxQueue) > 0) {
3494            $t->setOption('stroke', 'none');
3495            $t->setOption('style',
3496              "font-family:{$this->fontFamily};font-size:{$fSize}px");
3497            $boxQueue[count($boxQueue) - 1]->addText($t);
3498          } else {
3499            $this->svgObjects->addObject($t);
3500          }
3501        }
3502      }
3503    }
3504  }
3505
3506  /*
3507   * Allow specifying references that target an object starting at grid point
3508   * (ROW,COL). This allows styling of lines, boxes, or any text object.
3509   */
3510  private function injectCommands() {
3511    $boxes = $this->svgObjects->getGroup('boxes');
3512    $lines = $this->svgObjects->getGroup('lines');
3513    $text = $this->svgObjects->getGroup('text');
3514
3515    foreach ($boxes as $obj) {
3516      $objPoints = $obj->getPoints();
3517      $pointCmd = "{$objPoints[0]->gridY},{$objPoints[0]->gridX}";
3518
3519      if (isset($this->commands[$pointCmd])) {
3520        $obj->setOptions($this->commands[$pointCmd]);
3521      }
3522
3523      foreach ($obj->getText() as $text) {
3524        $textPoint = $text->getPoint();
3525        $pointCmd = "{$textPoint->gridY},{$textPoint->gridX}";
3526
3527        if (isset($this->commands[$pointCmd])) {
3528          $text->setOptions($this->commands[$pointCmd]);
3529        }
3530      }
3531    }
3532
3533    foreach ($lines as $obj) {
3534      $objPoints = $obj->getPoints();
3535      $pointCmd = "{$objPoints[0]->gridY},{$objPoints[0]->gridX}";
3536
3537      if (isset($this->commands[$pointCmd])) {
3538        $obj->setOptions($this->commands[$pointCmd]);
3539      }
3540    }
3541
3542    foreach ($text as $obj) {
3543      $objPoint = $obj->getPoint();
3544      $pointCmd = "{$objPoint->gridY},{$objPoint->gridX}";
3545
3546      if (isset($this->commands[$pointCmd])) {
3547        $obj->setOptions($this->commands[$pointCmd]);
3548      }
3549    }
3550  }
3551
3552  /*
3553   * A generic, recursive line walker. This walker makes the assumption that
3554   * lines want to go in the direction that they are already heading. I'm
3555   * sure that there are ways to formulate lines to screw this walker up,
3556   * but it does a good enough job right now.
3557   */
3558  private function walk($path, $row, $col, $dir, $d = 0) {
3559    $d++;
3560    $r = $row;
3561    $c = $col;
3562
3563    if ($dir == self::DIR_RIGHT || $dir == self::DIR_LEFT) {
3564      $cInc = ($dir == self::DIR_RIGHT) ? 1 : -1;
3565      $rInc = 0;
3566    } elseif ($dir == self::DIR_DOWN || $dir == self::DIR_UP) {
3567      $cInc = 0;
3568      $rInc = ($dir == self::DIR_DOWN) ? 1 : -1;
3569    } elseif ($dir == self::DIR_SE || $dir == self::DIR_NE) {
3570      $cInc = 1;
3571      $rInc = ($dir == self::DIR_SE) ? 1 : -1;
3572    }
3573
3574    /* Follow the edge for as long as we can */
3575    $cur = $this->getChar($r, $c);
3576    while ($this->isEdge($cur, $dir)) {
3577      if ($cur == ':' || $cur == '=') {
3578        $path->setOption('stroke-dasharray', '5 5');
3579      }
3580
3581      if ($this->isTick($cur)) {
3582        $path->addTick($c, $r, ($cur == 'o') ? Point::DOT : Point::TICK);
3583        $path->addPoint($c, $r);
3584      }
3585
3586      $c += $cInc;
3587      $r += $rInc;
3588      $cur = $this->getChar($r, $c);
3589    }
3590
3591    if ($this->isCorner($cur)) {
3592      if ($cur == '.' || $cur == "'") {
3593        $path->addPoint($c, $r, Point::CONTROL);
3594      } else {
3595        $path->addPoint($c, $r);
3596      }
3597
3598      if ($path->isClosed()) {
3599        $path->popPoint();
3600        return;
3601      }
3602
3603      /*
3604       * Attempt first to continue in the current direction. If we can't,
3605       * try to go in any direction other than the one opposite of where
3606       * we just came from -- no backtracking.
3607       */
3608      $n = $this->getChar($r - 1, $c);
3609      $s = $this->getChar($r + 1, $c);
3610      $e = $this->getChar($r, $c + 1);
3611      $w = $this->getChar($r, $c - 1);
3612      $next = $this->getChar($r + $rInc, $c + $cInc);
3613
3614      $se = $this->getChar($r + 1, $c + 1);
3615      $ne = $this->getChar($r - 1, $c + 1);
3616
3617      if ($this->isCorner($next) || $this->isEdge($next, $dir)) {
3618        return $this->walk($path, $r + $rInc, $c + $cInc, $dir, $d);
3619      } elseif ($dir != self::DIR_DOWN &&
3620                ($this->isCorner($n) || $this->isEdge($n, self::DIR_UP))) {
3621        /* Can't turn up into bottom corner */
3622        if (($cur != '.' && $cur != "'") || ($cur == '.' && $n != '.') ||
3623              ($cur == "'" && $n != "'")) {
3624          return $this->walk($path, $r - 1, $c, self::DIR_UP, $d);
3625        }
3626      } elseif ($dir != self::DIR_UP &&
3627                ($this->isCorner($s) || $this->isEdge($s, self::DIR_DOWN))) {
3628        /* Can't turn down into top corner */
3629        if (($cur != '.' && $cur != "'") || ($cur == '.' && $s != '.') ||
3630              ($cur == "'" && $s != "'")) {
3631          return $this->walk($path, $r + 1, $c, self::DIR_DOWN, $d);
3632        }
3633      } elseif ($dir != self::DIR_LEFT &&
3634                ($this->isCorner($e) || $this->isEdge($e, self::DIR_RIGHT))) {
3635        return $this->walk($path, $r, $c + 1, self::DIR_RIGHT, $d);
3636      } elseif ($dir != self::DIR_RIGHT &&
3637                ($this->isCorner($w) || $this->isEdge($w, self::DIR_LEFT))) {
3638        return $this->walk($path, $r, $c - 1, self::DIR_LEFT, $d);
3639      } elseif ($dir == self::DIR_SE &&
3640                ($this->isCorner($ne) || $this->isEdge($ne, self::DIR_NE))) {
3641        return $this->walk($path, $r - 1, $c + 1, self::DIR_NE, $d);
3642      } elseif ($dir == self::DIR_NE &&
3643                ($this->isCorner($se) || $this->isEdge($se, self::DIR_SE))) {
3644        return $this->walk($path, $r + 1, $c + 1, self::DIR_SE, $d);
3645      }
3646    } elseif ($this->isMarker($cur)) {
3647      /* We found a marker! Add it. */
3648      $path->addMarker($c, $r, Point::SMARKER);
3649      return;
3650    } else {
3651      /*
3652       * Not a corner, not a marker, and we already ate edges. Whatever this
3653       * is, it is not part of the line.
3654       */
3655      $path->addPoint($c - $cInc, $r - $rInc);
3656      return;
3657    }
3658  }
3659
3660  /*
3661   * This function attempts to follow a line and complete it into a closed
3662   * polygon. It assumes that we have been called from a top point, and in any
3663   * case that the polygon can be found by moving clockwise along its edges.
3664   * Any time this algorithm finds a corner, it attempts to turn right. If it
3665   * cannot turn right, it goes in any direction other than the one it came
3666   * from. If it cannot complete the polygon by continuing in any direction
3667   * from a point, that point is removed from the path, and we continue on
3668   * from the previous point (since this is a recursive function).
3669   *
3670   * Because the function assumes that it is starting from the top left,
3671   * if its first turn cannot be a right turn to moving down, the object
3672   * cannot be a valid polygon. It also maintains an internal list of points
3673   * it has already visited, and refuses to visit any point twice.
3674   */
3675  private function wallFollow($path, $r, $c, $dir, $bucket = array(), $d = 0) {
3676    $d++;
3677
3678    if ($dir == self::DIR_RIGHT || $dir == self::DIR_LEFT) {
3679      $cInc = ($dir == self::DIR_RIGHT) ? 1 : -1;
3680      $rInc = 0;
3681    } elseif ($dir == self::DIR_DOWN || $dir == self::DIR_UP) {
3682      $cInc = 0;
3683      $rInc = ($dir == self::DIR_DOWN) ? 1 : -1;
3684    }
3685
3686    /* Traverse the edge in whatever direction we are going. */
3687    $cur = $this->getChar($r, $c);
3688    while ($this->isBoxEdge($cur, $dir)) {
3689      $r += $rInc;
3690      $c += $cInc;
3691      $cur = $this->getChar($r, $c);
3692    }
3693
3694    /* We 'key' our location by catting r and c together */
3695    $key = "{$r}{$c}";
3696    if (isset($bucket[$key])) {
3697      return;
3698    }
3699
3700    /*
3701     * When we run into a corner, we have to make a somewhat complicated
3702     * decision about which direction to turn.
3703     */
3704    if ($this->isBoxCorner($cur)) {
3705      if (!isset($bucket[$key])) {
3706        $bucket[$key] = 0;
3707      }
3708
3709      switch ($cur) {
3710      case '.':
3711      case "'":
3712        $pointExists = $path->addPoint($c, $r, Point::CONTROL);
3713        break;
3714
3715      case '#':
3716        $pointExists = $path->addPoint($c, $r);
3717        break;
3718      }
3719
3720      if ($path->isClosed() || $pointExists) {
3721        return;
3722      }
3723
3724      /*
3725      * Special case: if we're looking for our first turn and we can't make it
3726      * due to incompatible corners, keep looking, but don't adjust our call
3727      * depth so that we can continue to make progress.
3728      */
3729      if ($d == 1 && $cur == '.' && $this->getChar($r + 1, $c) == '.') {
3730        return $this->wallFollow($path, $r, $c + 1, $dir, $bucket, 0);
3731      }
3732
3733      /*
3734       * We need to make a decision here on where to turn. We may have multiple
3735       * directions we can choose, and all of them might generate a closed
3736       * object. Always try turning right first.
3737       */
3738      $newDir = false;
3739      $n = $this->getChar($r - 1, $c);
3740      $s = $this->getChar($r + 1, $c);
3741      $e = $this->getChar($r, $c + 1);
3742      $w = $this->getChar($r, $c - 1);
3743
3744      if ($dir == self::DIR_RIGHT) {
3745        if (!($bucket[$key] & self::DIR_DOWN) &&
3746            ($this->isBoxEdge($s, self::DIR_DOWN) || $this->isBoxCorner($s))) {
3747          /* We can't turn into another top edge. */
3748          if (($cur != '.' && $cur != "'") || ($cur == '.' && $s != '.') ||
3749              ($cur == "'" && $s != "'")) {
3750            $newDir = self::DIR_DOWN;
3751          }
3752        } else {
3753          /* There is no right hand turn for us; this isn't a valid start */
3754          if ($d == 1) {
3755            return;
3756          }
3757        }
3758      } elseif ($dir == self::DIR_DOWN) {
3759        if (!($bucket[$key] & self::DIR_LEFT) &&
3760            ($this->isBoxEdge($w, self::DIR_LEFT) || $this->isBoxCorner($w))) {
3761          $newDir == self::DIR_LEFT;
3762        }
3763      } elseif ($dir == self::DIR_LEFT) {
3764        if (!($bucket[$key] & self::DIR_UP) &&
3765            ($this->isBoxEdge($n, self::DIR_UP) || $this->isBoxCorner($n))) {
3766          /* We can't turn into another bottom edge. */
3767          if (($cur != '.' && $cur != "'") || ($cur == '.' && $n != '.') ||
3768              ($cur == "'" && $n != "'")) {
3769            $newDir = self::DIR_UP;
3770          }
3771        }
3772      } elseif ($dir == self::DIR_UP) {
3773        if (!($bucket[$key] & self::DIR_RIGHT) &&
3774            ($this->isBoxEdge($e, self::DIR_RIGHT) || $this->isBoxCorner($e))) {
3775          $newDir = self::DIR_RIGHT;
3776        }
3777      }
3778
3779      if ($newDir != false) {
3780        if ($newDir == self::DIR_RIGHT || $newDir == self::DIR_LEFT) {
3781          $cMod = ($newDir == self::DIR_RIGHT) ? 1 : -1;
3782          $rMod = 0;
3783        } elseif ($newDir == self::DIR_DOWN || $newDir == self::DIR_UP) {
3784          $cMod = 0;
3785          $rMod = ($newDir == self::DIR_DOWN) ? 1 : -1;
3786        }
3787
3788        $bucket[$key] |= $newDir;
3789        $this->wallFollow($path, $r+$rMod, $c+$cMod, $newDir, $bucket, $d);
3790        if ($path->isClosed()) {
3791          return;
3792        }
3793      }
3794
3795      /*
3796       * Unfortunately, we couldn't complete the search by turning right,
3797       * so we need to pick a different direction. Note that this will also
3798       * eventually cause us to continue in the direction we were already
3799       * going. We make sure that we don't go in the direction opposite of
3800       * the one in which we're already headed, or an any direction we've
3801       * already travelled for this point (we may have hit it from an
3802       * earlier branch). We accept the first closing polygon as the
3803       * "correct" one for this object.
3804       */
3805      if ($dir != self::DIR_RIGHT && !($bucket[$key] & self::DIR_LEFT) &&
3806          ($this->isBoxEdge($w, self::DIR_LEFT) || $this->isBoxCorner($w))) {
3807        $bucket[$key] |= self::DIR_LEFT;
3808        $this->wallFollow($path, $r, $c - 1, self::DIR_LEFT, $bucket, $d);
3809        if ($path->isClosed()) {
3810          return;
3811        }
3812      }
3813      if ($dir != self::DIR_LEFT && !($bucket[$key] & self::DIR_RIGHT) &&
3814          ($this->isBoxEdge($e, self::DIR_RIGHT) || $this->isBoxCorner($e))) {
3815        $bucket[$key] |= self::DIR_RIGHT;
3816        $this->wallFollow($path, $r, $c + 1, self::DIR_RIGHT, $bucket, $d);
3817        if ($path->isClosed()) {
3818          return;
3819        }
3820      }
3821      if ($dir != self::DIR_DOWN && !($bucket[$key] & self::DIR_UP) &&
3822          ($this->isBoxEdge($n, self::DIR_UP) || $this->isBoxCorner($n))) {
3823          if (($cur != '.' && $cur != "'") || ($cur == '.' && $n != '.') ||
3824              ($cur == "'" && $n != "'")) {
3825          /* We can't turn into another bottom edge. */
3826          $bucket[$key] |= self::DIR_UP;
3827          $this->wallFollow($path, $r - 1, $c, self::DIR_UP, $bucket, $d);
3828          if ($path->isClosed()) {
3829            return;
3830          }
3831        }
3832      }
3833      if ($dir != self::DIR_UP && !($bucket[$key] & self::DIR_DOWN) &&
3834          ($this->isBoxEdge($s, self::DIR_DOWN) || $this->isBoxCorner($s))) {
3835          if (($cur != '.' && $cur != "'") || ($cur == '.' && $s != '.') ||
3836              ($cur == "'" && $s != "'")) {
3837          /* We can't turn into another top edge. */
3838          $bucket[$key] |= self::DIR_DOWN;
3839          $this->wallFollow($path, $r + 1, $c, self::DIR_DOWN, $bucket, $d);
3840          if ($path->isClosed()) {
3841            return;
3842          }
3843        }
3844      }
3845
3846      /*
3847       * If we get here, the path doesn't close in any direction from this
3848       * point (it's probably a line extension). Get rid of the point from our
3849       * path and go back to the last one.
3850       */
3851      $path->popPoint();
3852      return;
3853    } elseif ($this->isMarker($this->getChar($r, $c))) {
3854      /* Marker is part of a line, not a wall to close. */
3855      return;
3856    } else {
3857      /* We landed on some whitespace or something; this isn't a closed path */
3858      return;
3859    }
3860  }
3861
3862  /*
3863   * Clears an object from the grid, erasing all edge and marker points. This
3864   * function retains corners in "clearCorners" to be cleaned up before we do
3865   * text parsing.
3866   */
3867  private function clearObject($obj) {
3868    $points = $obj->getPoints();
3869    $closed = $obj->isClosed();
3870
3871    $bound = count($points);
3872    for ($i = 0; $i < $bound; $i++) {
3873      $p = $points[$i];
3874
3875      if ($i == count($points) - 1) {
3876        /* This keeps us from handling end of line to start of line */
3877        if ($closed) {
3878          $nP = $points[0];
3879        } else {
3880          $nP = null;
3881        }
3882      } else {
3883        $nP = $points[$i+1];
3884      }
3885
3886      /* If we're on the same vertical axis as our next point... */
3887      if ($nP != null && $p->gridX == $nP->gridX) {
3888        /* ...traverse the vertical line from the minimum to maximum points */
3889        $maxY = max($p->gridY, $nP->gridY);
3890        for ($j = min($p->gridY, $nP->gridY); $j <= $maxY; $j++) {
3891          $char = $this->getChar($j, $p->gridX);
3892
3893          if (!$this->isTick($char) && $this->isEdge($char) || $this->isMarker($char)) {
3894            $this->grid[$j][$p->gridX] = ' ';
3895          } elseif ($this->isCorner($char)) {
3896            $this->clearCorners[] = array($j, $p->gridX);
3897          } elseif ($this->isTick($char)) {
3898            $this->grid[$j][$p->gridX] = '+';
3899          }
3900        }
3901      } elseif ($nP != null && $p->gridY == $nP->gridY) {
3902        /* Same horizontal plane; traverse from min to max point */
3903        $maxX = max($p->gridX, $nP->gridX);
3904        for ($j = min($p->gridX, $nP->gridX); $j <= $maxX; $j++) {
3905          $char = $this->getChar($p->gridY, $j);
3906
3907          if (!$this->isTick($char) && $this->isEdge($char) || $this->isMarker($char)) {
3908            $this->grid[$p->gridY][$j] = ' ';
3909          } elseif ($this->isCorner($char)) {
3910            $this->clearCorners[] = array($p->gridY, $j);
3911          } elseif ($this->isTick($char)) {
3912            $this->grid[$p->gridY][$j] = '+';
3913          }
3914        }
3915      } elseif ($nP != null && $closed == false && $p->gridX != $nP->gridX &&
3916                $p->gridY != $nP->gridY) {
3917        /*
3918         * This is a diagonal line starting from the westernmost point. It
3919         * must contain max(p->gridY, nP->gridY) - min(p->gridY, nP->gridY)
3920         * segments, and we can tell whether to go north or south depending
3921         * on which side of zero p->gridY - nP->gridY lies. There are no
3922         * corners in diagonals, so we don't have to keep those around.
3923         */
3924        $c = $p->gridX;
3925        $r = $p->gridY;
3926        $rInc = ($p->gridY > $nP->gridY) ? -1 : 1;
3927        $bound = max($p->gridY, $nP->gridY) - min($p->gridY, $nP->gridY);
3928
3929        /*
3930         * This looks like an off-by-one, but it is not. This clears the
3931         * corner, if one exists.
3932         */
3933        for ($j = 0; $j <= $bound; $j++) {
3934          $char = $this->getChar($r, $c);
3935          if ($char == '/' || $char == "\\" || $this->isMarker($char)) {
3936            $this->grid[$r][$c++] = ' ';
3937          } elseif ($this->isCorner($char)) {
3938            $this->clearCorners[] = array($r, $c++);
3939          } elseif ($this->isTick($char)) {
3940            $this->grid[$r][$c] = '+';
3941          }
3942          $r += $rInc;
3943        }
3944
3945        $this->grid[$p->gridY][$p->gridX] = ' ';
3946        break;
3947      }
3948    }
3949  }
3950
3951  /*
3952   * Find style information for this polygon. This information is required to
3953   * exist on the first line after the top, touching the left wall. It's kind
3954   * of a pain requirement, but there's not a much better way to do it:
3955   * ditaa's handling requires too much text flung everywhere and this way
3956   * gives you a good method for specifying *tons* of information about the
3957   * object.
3958   */
3959  private function findCommands($box) {
3960    $points = $box->getPoints();
3961    $sX = $points[0]->gridX + 1;
3962    $sY = $points[0]->gridY + 1;
3963    $ref = '';
3964    if ($this->getChar($sY, $sX++) == '[') {
3965      $char = $this->getChar($sY, $sX++);
3966      while ($char != ']') {
3967        $ref .= $char;
3968        $char = $this->getChar($sY, $sX++);
3969      }
3970
3971      if ($char == ']') {
3972        $sX = $points[0]->gridX + 1;
3973        $sY = $points[0]->gridY + 1;
3974
3975        if (!isset($this->commands[$ref]['a2s:delref']) &&
3976            !isset($this->commands[$ref]['a2s:label'])) {
3977          $this->grid[$sY][$sX] = ' ';
3978          $this->grid[$sY][$sX + strlen($ref) + 1] = ' ';
3979        } else {
3980          if (isset($this->commands[$ref]['a2s:label'])) {
3981            $label = $this->commands[$ref]['a2s:label'];
3982          } else {
3983            $label = null;
3984          }
3985
3986          $len = strlen($ref) + 2;
3987          for ($i = 0; $i < $len; $i++) {
3988            if (strlen($label) > $i) {
3989              $this->grid[$sY][$sX + $i] = substr($label, $i, 1);
3990            } else {
3991              $this->grid[$sY][$sX + $i] = ' ';
3992            }
3993          }
3994        }
3995
3996        if (isset($this->commands[$ref])) {
3997          $box->setOptions($this->commands[$ref]);
3998        }
3999      }
4000    }
4001
4002    return $ref;
4003  }
4004
4005  /*
4006   * Extremely useful debugging information to figure out what has been
4007   * parsed, especially when used in conjunction with clearObject.
4008   */
4009  private function dumpGrid() {
4010    foreach($this->grid as $lines) {
4011      echo implode('', $lines) . "\n";
4012    }
4013  }
4014
4015  private function getChar($row, $col) {
4016    if (isset($this->grid[$row][$col])) {
4017      return $this->grid[$row][$col];
4018    }
4019
4020    return null;
4021  }
4022
4023  private function isBoxEdge($char, $dir = null) {
4024    if ($dir == null) {
4025      return $char == '-' || $char == '|' || char == ':' || $char == '=' || $char == '*' || $char == '+';
4026    } elseif ($dir == self::DIR_UP || $dir == self::DIR_DOWN) {
4027      return $char == '|' || $char == ':' || $char == '*' || $char == '+';
4028    } elseif ($dir == self::DIR_LEFT || $dir == self::DIR_RIGHT) {
4029      return $char == '-' || $char == '=' || $char == '*' || $char == '+';
4030    }
4031  }
4032
4033  private function isEdge($char, $dir = null) {
4034    if ($char == 'o' || $char == 'x') {
4035      return true;
4036    }
4037
4038    if ($dir == null) {
4039      return $char == '-' || $char == '|' || $char == ':' || $char == '=' || $char == '*' || $char == '/' || $char == "\\";
4040    } elseif ($dir == self::DIR_UP || $dir == self::DIR_DOWN) {
4041      return $char == '|' || $char == ':' || $char == '*';
4042    } elseif ($dir == self::DIR_LEFT || $dir == self::DIR_RIGHT) {
4043      return $char == '-' || $char == '=' || $char == '*';
4044    } elseif ($dir == self::DIR_NE) {
4045      return $char == '/';
4046    } elseif ($dir == self::DIR_SE) {
4047      return $char == "\\";
4048    }
4049  }
4050
4051  private function isBoxCorner($char) {
4052    return $char == '.' || $char == "'" || $char == '#';
4053  }
4054
4055  private function isCorner($char) {
4056    return $char == '.' || $char == "'" || $char == '#' || $char == '+';
4057  }
4058
4059  private function isMarker($char) {
4060    return $char == 'v' || $char == '^' || $char == '<' || $char == '>';
4061  }
4062
4063  private function isTick($char) {
4064    return $char == 'o' || $char == 'x';
4065  }
4066}
4067
4068/* vim:ts=2:sw=2:et:
4069 *  * */
4070