001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.parser;
020:
021: import java.io.IOException;
022:
023: /**
024: * This class implements an event-based parser for the SVG path's d
025: * attribute values.
026: *
027: * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
028: * @version $Id: PathParser.java 502167 2007-02-01 09:26:51Z dvholten $
029: */
030: public class PathParser extends NumberParser {
031:
032: /**
033: * The path handler used to report parse events.
034: */
035: protected PathHandler pathHandler;
036:
037: /**
038: * Creates a new PathParser.
039: */
040: public PathParser() {
041: pathHandler = DefaultPathHandler.INSTANCE;
042: }
043:
044: /**
045: * Allows an application to register a path handler.
046: *
047: * <p>If the application does not register a handler, all
048: * events reported by the parser will be silently ignored.
049: *
050: * <p>Applications may register a new or different handler in the
051: * middle of a parse, and the parser must begin using the new
052: * handler immediately.</p>
053: * @param handler The transform list handler.
054: */
055: public void setPathHandler(PathHandler handler) {
056: pathHandler = handler;
057: }
058:
059: /**
060: * Returns the path handler in use.
061: */
062: public PathHandler getPathHandler() {
063: return pathHandler;
064: }
065:
066: protected void doParse() throws ParseException, IOException {
067: pathHandler.startPath();
068:
069: current = reader.read();
070: loop: for (;;) {
071: try {
072: switch (current) {
073: case 0xD:
074: case 0xA:
075: case 0x20:
076: case 0x9:
077: current = reader.read();
078: break;
079: case 'z':
080: case 'Z':
081: current = reader.read();
082: pathHandler.closePath();
083: break;
084: case 'm':
085: parsem();
086: break;
087: case 'M':
088: parseM();
089: break;
090: case 'l':
091: parsel();
092: break;
093: case 'L':
094: parseL();
095: break;
096: case 'h':
097: parseh();
098: break;
099: case 'H':
100: parseH();
101: break;
102: case 'v':
103: parsev();
104: break;
105: case 'V':
106: parseV();
107: break;
108: case 'c':
109: parsec();
110: break;
111: case 'C':
112: parseC();
113: break;
114: case 'q':
115: parseq();
116: break;
117: case 'Q':
118: parseQ();
119: break;
120: case 's':
121: parses();
122: break;
123: case 'S':
124: parseS();
125: break;
126: case 't':
127: parset();
128: break;
129: case 'T':
130: parseT();
131: break;
132: case 'a':
133: parsea();
134: break;
135: case 'A':
136: parseA();
137: break;
138: case -1:
139: break loop;
140: default:
141: reportUnexpected(current);
142: break;
143: }
144: } catch (ParseException e) {
145: errorHandler.error(e);
146: skipSubPath();
147: }
148: }
149:
150: skipSpaces();
151: if (current != -1) {
152: reportError("end.of.stream.expected",
153: new Object[] { new Integer(current) });
154: }
155:
156: pathHandler.endPath();
157: }
158:
159: /**
160: * Parses a 'm' command.
161: */
162: protected void parsem() throws ParseException, IOException {
163: current = reader.read();
164: skipSpaces();
165:
166: float x = parseFloat();
167: skipCommaSpaces();
168: float y = parseFloat();
169: pathHandler.movetoRel(x, y);
170:
171: boolean expectNumber = skipCommaSpaces2();
172: _parsel(expectNumber);
173: }
174:
175: /**
176: * Parses a 'M' command.
177: */
178: protected void parseM() throws ParseException, IOException {
179: current = reader.read();
180: skipSpaces();
181:
182: float x = parseFloat();
183: skipCommaSpaces();
184: float y = parseFloat();
185: pathHandler.movetoAbs(x, y);
186:
187: boolean expectNumber = skipCommaSpaces2();
188: _parseL(expectNumber);
189: }
190:
191: /**
192: * Parses a 'l' command.
193: */
194: protected void parsel() throws ParseException, IOException {
195: current = reader.read();
196: skipSpaces();
197: _parsel(true);
198: }
199:
200: protected void _parsel(boolean expectNumber) throws ParseException,
201: IOException {
202: for (;;) {
203: switch (current) {
204: default:
205: if (expectNumber)
206: reportUnexpected(current);
207: return;
208: case '+':
209: case '-':
210: case '.':
211: case '0':
212: case '1':
213: case '2':
214: case '3':
215: case '4':
216: case '5':
217: case '6':
218: case '7':
219: case '8':
220: case '9':
221: break;
222: }
223: float x = parseFloat();
224: skipCommaSpaces();
225: float y = parseFloat();
226:
227: pathHandler.linetoRel(x, y);
228: expectNumber = skipCommaSpaces2();
229: }
230: }
231:
232: /**
233: * Parses a 'L' command.
234: */
235: protected void parseL() throws ParseException, IOException {
236: current = reader.read();
237: skipSpaces();
238: _parseL(true);
239: }
240:
241: protected void _parseL(boolean expectNumber) throws ParseException,
242: IOException {
243: for (;;) {
244: switch (current) {
245: default:
246: if (expectNumber)
247: reportUnexpected(current);
248: return;
249: case '+':
250: case '-':
251: case '.':
252: case '0':
253: case '1':
254: case '2':
255: case '3':
256: case '4':
257: case '5':
258: case '6':
259: case '7':
260: case '8':
261: case '9':
262: break;
263: }
264: float x = parseFloat();
265: skipCommaSpaces();
266: float y = parseFloat();
267:
268: pathHandler.linetoAbs(x, y);
269: expectNumber = skipCommaSpaces2();
270: }
271: }
272:
273: /**
274: * Parses a 'h' command.
275: */
276: protected void parseh() throws ParseException, IOException {
277: current = reader.read();
278: skipSpaces();
279: boolean expectNumber = true;
280:
281: for (;;) {
282: switch (current) {
283: default:
284: if (expectNumber)
285: reportUnexpected(current);
286: return;
287: case '+':
288: case '-':
289: case '.':
290: case '0':
291: case '1':
292: case '2':
293: case '3':
294: case '4':
295: case '5':
296: case '6':
297: case '7':
298: case '8':
299: case '9':
300: break;
301: }
302: float x = parseFloat();
303: pathHandler.linetoHorizontalRel(x);
304: expectNumber = skipCommaSpaces2();
305: }
306: }
307:
308: /**
309: * Parses a 'H' command.
310: */
311: protected void parseH() throws ParseException, IOException {
312: current = reader.read();
313: skipSpaces();
314: boolean expectNumber = true;
315:
316: for (;;) {
317: switch (current) {
318: default:
319: if (expectNumber)
320: reportUnexpected(current);
321: return;
322:
323: case '+':
324: case '-':
325: case '.':
326: case '0':
327: case '1':
328: case '2':
329: case '3':
330: case '4':
331: case '5':
332: case '6':
333: case '7':
334: case '8':
335: case '9':
336: break;
337: }
338: float x = parseFloat();
339: pathHandler.linetoHorizontalAbs(x);
340: expectNumber = skipCommaSpaces2();
341: }
342: }
343:
344: /**
345: * Parses a 'v' command.
346: */
347: protected void parsev() throws ParseException, IOException {
348: current = reader.read();
349: skipSpaces();
350: boolean expectNumber = true;
351:
352: for (;;) {
353: switch (current) {
354: default:
355: if (expectNumber)
356: reportUnexpected(current);
357: return;
358:
359: case '+':
360: case '-':
361: case '.':
362: case '0':
363: case '1':
364: case '2':
365: case '3':
366: case '4':
367: case '5':
368: case '6':
369: case '7':
370: case '8':
371: case '9':
372: break;
373: }
374: float x = parseFloat();
375:
376: pathHandler.linetoVerticalRel(x);
377: expectNumber = skipCommaSpaces2();
378: }
379: }
380:
381: /**
382: * Parses a 'V' command.
383: */
384: protected void parseV() throws ParseException, IOException {
385: current = reader.read();
386: skipSpaces();
387: boolean expectNumber = true;
388:
389: for (;;) {
390: switch (current) {
391: default:
392: if (expectNumber)
393: reportUnexpected(current);
394: return;
395:
396: case '+':
397: case '-':
398: case '.':
399: case '0':
400: case '1':
401: case '2':
402: case '3':
403: case '4':
404: case '5':
405: case '6':
406: case '7':
407: case '8':
408: case '9':
409: break;
410: }
411: float x = parseFloat();
412:
413: pathHandler.linetoVerticalAbs(x);
414: expectNumber = skipCommaSpaces2();
415: }
416: }
417:
418: /**
419: * Parses a 'c' command.
420: */
421: protected void parsec() throws ParseException, IOException {
422: current = reader.read();
423: skipSpaces();
424: boolean expectNumber = true;
425:
426: for (;;) {
427: switch (current) {
428: default:
429: if (expectNumber)
430: reportUnexpected(current);
431: return;
432:
433: case '+':
434: case '-':
435: case '.':
436: case '0':
437: case '1':
438: case '2':
439: case '3':
440: case '4':
441: case '5':
442: case '6':
443: case '7':
444: case '8':
445: case '9':
446: break;
447: }
448:
449: float x1 = parseFloat();
450: skipCommaSpaces();
451: float y1 = parseFloat();
452: skipCommaSpaces();
453: float x2 = parseFloat();
454: skipCommaSpaces();
455: float y2 = parseFloat();
456: skipCommaSpaces();
457: float x = parseFloat();
458: skipCommaSpaces();
459: float y = parseFloat();
460:
461: pathHandler.curvetoCubicRel(x1, y1, x2, y2, x, y);
462: expectNumber = skipCommaSpaces2();
463: }
464: }
465:
466: /**
467: * Parses a 'C' command.
468: */
469: protected void parseC() throws ParseException, IOException {
470: current = reader.read();
471: skipSpaces();
472: boolean expectNumber = true;
473:
474: for (;;) {
475: switch (current) {
476: default:
477: if (expectNumber)
478: reportUnexpected(current);
479: return;
480:
481: case '+':
482: case '-':
483: case '.':
484: case '0':
485: case '1':
486: case '2':
487: case '3':
488: case '4':
489: case '5':
490: case '6':
491: case '7':
492: case '8':
493: case '9':
494: break;
495: }
496:
497: float x1 = parseFloat();
498: skipCommaSpaces();
499: float y1 = parseFloat();
500: skipCommaSpaces();
501: float x2 = parseFloat();
502: skipCommaSpaces();
503: float y2 = parseFloat();
504: skipCommaSpaces();
505: float x = parseFloat();
506: skipCommaSpaces();
507: float y = parseFloat();
508:
509: pathHandler.curvetoCubicAbs(x1, y1, x2, y2, x, y);
510: expectNumber = skipCommaSpaces2();
511: }
512: }
513:
514: /**
515: * Parses a 'q' command.
516: */
517: protected void parseq() throws ParseException, IOException {
518: current = reader.read();
519: skipSpaces();
520: boolean expectNumber = true;
521:
522: for (;;) {
523: switch (current) {
524: default:
525: if (expectNumber)
526: reportUnexpected(current);
527: return;
528:
529: case '+':
530: case '-':
531: case '.':
532: case '0':
533: case '1':
534: case '2':
535: case '3':
536: case '4':
537: case '5':
538: case '6':
539: case '7':
540: case '8':
541: case '9':
542: break;
543: }
544:
545: float x1 = parseFloat();
546: skipCommaSpaces();
547: float y1 = parseFloat();
548: skipCommaSpaces();
549: float x = parseFloat();
550: skipCommaSpaces();
551: float y = parseFloat();
552:
553: pathHandler.curvetoQuadraticRel(x1, y1, x, y);
554: expectNumber = skipCommaSpaces2();
555: }
556: }
557:
558: /**
559: * Parses a 'Q' command.
560: */
561: protected void parseQ() throws ParseException, IOException {
562: current = reader.read();
563: skipSpaces();
564: boolean expectNumber = true;
565:
566: for (;;) {
567: switch (current) {
568: default:
569: if (expectNumber)
570: reportUnexpected(current);
571: return;
572:
573: case '+':
574: case '-':
575: case '.':
576: case '0':
577: case '1':
578: case '2':
579: case '3':
580: case '4':
581: case '5':
582: case '6':
583: case '7':
584: case '8':
585: case '9':
586: break;
587: }
588:
589: float x1 = parseFloat();
590: skipCommaSpaces();
591: float y1 = parseFloat();
592: skipCommaSpaces();
593: float x = parseFloat();
594: skipCommaSpaces();
595: float y = parseFloat();
596:
597: pathHandler.curvetoQuadraticAbs(x1, y1, x, y);
598: expectNumber = skipCommaSpaces2();
599: }
600: }
601:
602: /**
603: * Parses a 's' command.
604: */
605: protected void parses() throws ParseException, IOException {
606: current = reader.read();
607: skipSpaces();
608: boolean expectNumber = true;
609:
610: for (;;) {
611: switch (current) {
612: default:
613: if (expectNumber)
614: reportUnexpected(current);
615: return;
616:
617: case '+':
618: case '-':
619: case '.':
620: case '0':
621: case '1':
622: case '2':
623: case '3':
624: case '4':
625: case '5':
626: case '6':
627: case '7':
628: case '8':
629: case '9':
630: break;
631: }
632:
633: float x2 = parseFloat();
634: skipCommaSpaces();
635: float y2 = parseFloat();
636: skipCommaSpaces();
637: float x = parseFloat();
638: skipCommaSpaces();
639: float y = parseFloat();
640:
641: pathHandler.curvetoCubicSmoothRel(x2, y2, x, y);
642: expectNumber = skipCommaSpaces2();
643: }
644: }
645:
646: /**
647: * Parses a 'S' command.
648: */
649: protected void parseS() throws ParseException, IOException {
650: current = reader.read();
651: skipSpaces();
652: boolean expectNumber = true;
653:
654: for (;;) {
655: switch (current) {
656: default:
657: if (expectNumber)
658: reportUnexpected(current);
659: return;
660:
661: case '+':
662: case '-':
663: case '.':
664: case '0':
665: case '1':
666: case '2':
667: case '3':
668: case '4':
669: case '5':
670: case '6':
671: case '7':
672: case '8':
673: case '9':
674: break;
675: }
676:
677: float x2 = parseFloat();
678: skipCommaSpaces();
679: float y2 = parseFloat();
680: skipCommaSpaces();
681: float x = parseFloat();
682: skipCommaSpaces();
683: float y = parseFloat();
684:
685: pathHandler.curvetoCubicSmoothAbs(x2, y2, x, y);
686: expectNumber = skipCommaSpaces2();
687: }
688: }
689:
690: /**
691: * Parses a 't' command.
692: */
693: protected void parset() throws ParseException, IOException {
694: current = reader.read();
695: skipSpaces();
696: boolean expectNumber = true;
697:
698: for (;;) {
699: switch (current) {
700: default:
701: if (expectNumber)
702: reportUnexpected(current);
703: return;
704:
705: case '+':
706: case '-':
707: case '.':
708: case '0':
709: case '1':
710: case '2':
711: case '3':
712: case '4':
713: case '5':
714: case '6':
715: case '7':
716: case '8':
717: case '9':
718: break;
719: }
720:
721: float x = parseFloat();
722: skipCommaSpaces();
723: float y = parseFloat();
724:
725: pathHandler.curvetoQuadraticSmoothRel(x, y);
726: expectNumber = skipCommaSpaces2();
727: }
728: }
729:
730: /**
731: * Parses a 'T' command.
732: */
733: protected void parseT() throws ParseException, IOException {
734: current = reader.read();
735: skipSpaces();
736: boolean expectNumber = true;
737:
738: for (;;) {
739: switch (current) {
740: default:
741: if (expectNumber)
742: reportUnexpected(current);
743: return;
744:
745: case '+':
746: case '-':
747: case '.':
748: case '0':
749: case '1':
750: case '2':
751: case '3':
752: case '4':
753: case '5':
754: case '6':
755: case '7':
756: case '8':
757: case '9':
758: break;
759: }
760:
761: float x = parseFloat();
762: skipCommaSpaces();
763: float y = parseFloat();
764:
765: pathHandler.curvetoQuadraticSmoothAbs(x, y);
766: expectNumber = skipCommaSpaces2();
767: }
768: }
769:
770: /**
771: * Parses a 'a' command.
772: */
773: protected void parsea() throws ParseException, IOException {
774: current = reader.read();
775: skipSpaces();
776: boolean expectNumber = true;
777:
778: for (;;) {
779: switch (current) {
780: default:
781: if (expectNumber)
782: reportUnexpected(current);
783: return;
784:
785: case '+':
786: case '-':
787: case '.':
788: case '0':
789: case '1':
790: case '2':
791: case '3':
792: case '4':
793: case '5':
794: case '6':
795: case '7':
796: case '8':
797: case '9':
798: break;
799: }
800:
801: float rx = parseFloat();
802: skipCommaSpaces();
803: float ry = parseFloat();
804: skipCommaSpaces();
805: float ax = parseFloat();
806: skipCommaSpaces();
807:
808: boolean laf;
809: switch (current) {
810: default:
811: reportUnexpected(current);
812: return;
813: case '0':
814: laf = false;
815: break;
816: case '1':
817: laf = true;
818: break;
819: }
820:
821: current = reader.read();
822: skipCommaSpaces();
823:
824: boolean sf;
825: switch (current) {
826: default:
827: reportUnexpected(current);
828: return;
829: case '0':
830: sf = false;
831: break;
832: case '1':
833: sf = true;
834: break;
835: }
836:
837: current = reader.read();
838: skipCommaSpaces();
839:
840: float x = parseFloat();
841: skipCommaSpaces();
842: float y = parseFloat();
843:
844: pathHandler.arcRel(rx, ry, ax, laf, sf, x, y);
845: expectNumber = skipCommaSpaces2();
846: }
847: }
848:
849: /**
850: * Parses a 'A' command.
851: */
852: protected void parseA() throws ParseException, IOException {
853: current = reader.read();
854: skipSpaces();
855: boolean expectNumber = true;
856:
857: for (;;) {
858: switch (current) {
859: default:
860: if (expectNumber)
861: reportUnexpected(current);
862: return;
863:
864: case '+':
865: case '-':
866: case '.':
867: case '0':
868: case '1':
869: case '2':
870: case '3':
871: case '4':
872: case '5':
873: case '6':
874: case '7':
875: case '8':
876: case '9':
877: break;
878: }
879:
880: float rx = parseFloat();
881: skipCommaSpaces();
882: float ry = parseFloat();
883: skipCommaSpaces();
884: float ax = parseFloat();
885: skipCommaSpaces();
886:
887: boolean laf;
888: switch (current) {
889: default:
890: reportUnexpected(current);
891: return;
892: case '0':
893: laf = false;
894: break;
895: case '1':
896: laf = true;
897: break;
898: }
899:
900: current = reader.read();
901: skipCommaSpaces();
902:
903: boolean sf;
904: switch (current) {
905: default:
906: reportUnexpected(current);
907: return;
908: case '0':
909: sf = false;
910: break;
911: case '1':
912: sf = true;
913: break;
914: }
915:
916: current = reader.read();
917: skipCommaSpaces();
918: float x = parseFloat();
919: skipCommaSpaces();
920: float y = parseFloat();
921:
922: pathHandler.arcAbs(rx, ry, ax, laf, sf, x, y);
923: expectNumber = skipCommaSpaces2();
924: }
925: }
926:
927: /**
928: * Skips a sub-path.
929: */
930: protected void skipSubPath() throws ParseException, IOException {
931: for (;;) {
932: switch (current) {
933: case -1:
934: case 'm':
935: case 'M':
936: return;
937: default:
938: break;
939: }
940: current = reader.read();
941: }
942: }
943:
944: protected void reportUnexpected(int ch) throws ParseException,
945: IOException {
946: reportUnexpectedCharacterError(current);
947: skipSubPath();
948: }
949:
950: /**
951: * Skips the whitespaces and an optional comma.
952: * @return true if comma was skipped.
953: */
954: protected boolean skipCommaSpaces2() throws IOException {
955: wsp1: for (;;) {
956: switch (current) {
957: default:
958: break wsp1;
959: case 0x20:
960: case 0x9:
961: case 0xD:
962: case 0xA:
963: break;
964: }
965: current = reader.read();
966: }
967:
968: if (current != ',')
969: return false; // no comma.
970:
971: wsp2: for (;;) {
972: switch (current = reader.read()) {
973: default:
974: break wsp2;
975: case 0x20:
976: case 0x9:
977: case 0xD:
978: case 0xA:
979: break;
980: }
981: }
982: return true; // had comma
983: }
984: }
|