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('&', '<', '>'); 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