001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.perseus.j2d;
027:
028: import org.w3c.dom.DOMException;
029:
030: import org.w3c.dom.svg.SVGPath;
031:
032: /**
033: * Class for Path Data.
034: *
035: * @version $Id: Path.java,v 1.7 2006/04/21 06:35:11 st125089 Exp $
036: */
037: public class Path implements SVGPath {
038: /**
039: * The event-odd winding rule.
040: */
041: public static final int WIND_EVEN_ODD = 0;
042:
043: /**
044: * The non-zero winding rule.
045: */
046: public static final int WIND_NON_ZERO = 1;
047:
048: /**
049: * The default initial number of commands.
050: */
051: static final int INITIAL_COMMAND_CAPACITY = 10;
052:
053: /**
054: * The default initial number of data entries.
055: */
056: static final int INITIAL_DATA_CAPACITY = 40;
057:
058: /**
059: * The internal value for moveTo commands.
060: */
061: public static final byte MOVE_TO_IMPL = 0;
062:
063: /**
064: * The internal value for lineTo commands
065: */
066: public static final byte LINE_TO_IMPL = 1;
067:
068: /**
069: * The internal value for quadTo commands
070: */
071: public static final byte QUAD_TO_IMPL = 2;
072:
073: /**
074: * The internal value for curveTo commands.
075: */
076: public static final byte CURVE_TO_IMPL = 3;
077:
078: /**
079: * The internal value for close commands.
080: */
081: public static final int CLOSE_IMPL = 4;
082:
083: /**
084: * Delta offset for each command type.
085: * 2 for moveto and lineto.
086: * 4 for quadto.
087: * 6 for curveto.
088: * 0 for close.
089: */
090: public static final int[] COMMAND_OFFSET = { 2, 2, 4, 6, 0 };
091:
092: /**
093: * An array storing the path command types. One of MOVE_TO_IMPL,
094: * LINE_TO_IMPL, QUAD_TO_IMPL, CURVE_TO_IMPL.
095: */
096: protected byte[] commands;
097:
098: /**
099: * An array storing the path data. The array is a list of even
100: * length made of consecutive x/y coordinate pairs.
101: */
102: protected float[] data;
103:
104: /**
105: * The current number of segments.
106: */
107: protected int nSegments;
108:
109: /**
110: * The number of used entries in the data array.
111: */
112: protected int nData;
113:
114: /**
115: * Keeps track of the last requested command index.
116: */
117: protected int lastCmdIndex;
118:
119: /**
120: * The offset in the data array for the last requested command.
121: * This is used to minimize the computation of the offset
122: * in getSegmentParam.
123: */
124: protected int lastOffset;
125:
126: /**
127: *
128: */
129: public int getNumberOfSegments() {
130: return nSegments;
131: }
132:
133: /**
134: * Default constructor. The initial capacity is defined by
135: * the INITIAL_COMMAND_CAPACITY and INITIAL_DATA_CAPACITY
136: * static variables.
137: */
138: public Path() {
139: this (INITIAL_COMMAND_CAPACITY, INITIAL_DATA_CAPACITY);
140: }
141:
142: /**
143: * Initial constructor. The initial capacity is defined by
144: * the capacity parameters.
145: *
146: * @param commandCapacity the number of intended commands for
147: * this object. Must be positive or zero.
148: * @param dataCapacity the number of intended data parameters
149: * for this object. Must be positive or zero.
150: */
151: public Path(final int commandCapacity, final int dataCapacity) {
152: commands = new byte[commandCapacity];
153: data = new float[dataCapacity];
154: }
155:
156: /**
157: * Copy constructor.
158: *
159: * @param model the Path object to duplicate.
160: */
161: public Path(final Path model) {
162: if (model != null) {
163: // Copy path data.
164: this .data = new float[model.data.length];
165: System.arraycopy(model.data, 0, data, 0, model.data.length);
166:
167: // Copy command data
168: this .commands = new byte[model.commands.length];
169: System.arraycopy(model.commands, 0, commands, 0,
170: model.commands.length);
171:
172: this .nData = model.nData;
173: this .nSegments = model.nSegments;
174: } // else, use default values
175: }
176:
177: /**
178: * @return this path's commands data.
179: */
180: public byte[] getCommands() {
181: return commands;
182: }
183:
184: /**
185: * @return this path's data.
186: */
187: public float[] getData() {
188: return data;
189: }
190:
191: /**
192: * @param newData the new path data. The Path is only guaranteed to work after
193: * this method is invoked if the input data array has at least as many
194: * entries as the current path data array and if each entry is a float
195: * array of at least two values.
196: */
197: public void setData(final float[] newData) {
198: if (nData == 0) {
199: return;
200: }
201:
202: this .data = newData;
203: }
204:
205: /*
206: * Converts the internal command value to the SVGPath command type.
207: *
208: * @param command the internal command value, one of the XYZ_IMPL
209: * values.
210: * @return one of the SVGPath command constants.
211: */
212: static short toSVGPathCommand(final int command) {
213: switch (command) {
214: case MOVE_TO_IMPL:
215: return SVGPath.MOVE_TO;
216: case LINE_TO_IMPL:
217: return SVGPath.LINE_TO;
218: case QUAD_TO_IMPL:
219: return SVGPath.QUAD_TO;
220: case CURVE_TO_IMPL:
221: return SVGPath.CURVE_TO;
222: case CLOSE_IMPL:
223: default:
224: return SVGPath.CLOSE;
225: }
226: }
227:
228: /**
229: *
230: */
231: public short getSegment(final int cmdIndex) throws DOMException {
232: if (cmdIndex >= nSegments || cmdIndex < 0) {
233: throw new DOMException(DOMException.INDEX_SIZE_ERR, "");
234: }
235:
236: return toSVGPathCommand(commands[cmdIndex]);
237: }
238:
239: /**
240: *
241: */
242: public float getSegmentParam(final int cmdIndex,
243: final int paramIndex) throws DOMException {
244: if (cmdIndex >= nSegments || cmdIndex < 0 || paramIndex < 0) {
245: throw new DOMException(DOMException.INDEX_SIZE_ERR, "");
246: }
247:
248: switch (commands[cmdIndex]) {
249: case CLOSE_IMPL:
250: throw new DOMException(DOMException.INDEX_SIZE_ERR, "");
251: case LINE_TO_IMPL:
252: case MOVE_TO_IMPL:
253: if (paramIndex > 1) {
254: throw new DOMException(DOMException.INDEX_SIZE_ERR, "");
255: }
256: break;
257: case QUAD_TO_IMPL:
258: if (paramIndex > 3) {
259: throw new DOMException(DOMException.INDEX_SIZE_ERR, "");
260: }
261: break;
262: case CURVE_TO_IMPL:
263: if (paramIndex > 5) {
264: throw new DOMException(DOMException.INDEX_SIZE_ERR, "");
265: }
266: break;
267: default:
268: throw new DOMException(DOMException.INDEX_SIZE_ERR, "");
269: }
270:
271: return data[checkOffset(cmdIndex) + paramIndex];
272: }
273:
274: /**
275: * Implementation helper. Sets the lastCmdIndex to the requested index
276: * after computing the offset corresponding to that index.
277: *
278: * @param cmdIndex the new index for which the offset should be computed.
279: */
280: final int checkOffset(final int cmdIndex) {
281: if (cmdIndex == lastCmdIndex) {
282: return lastOffset;
283: }
284:
285: if (cmdIndex > lastCmdIndex) {
286: // The new index is _after_ the previous one. Add offsets for all
287: // commands between the two indices.
288: for (int ci = lastCmdIndex; ci < cmdIndex; ci++) {
289: lastOffset += COMMAND_OFFSET[commands[ci]];
290: }
291: } else {
292: // The next index is _before_ the previous one. Remove offsets for
293: // all commands, between the two indices.
294: for (int ci = lastCmdIndex - 1; ci >= cmdIndex; ci--) {
295: lastOffset -= COMMAND_OFFSET[commands[ci]];
296: }
297: }
298:
299: lastCmdIndex = cmdIndex;
300: return lastOffset;
301: }
302:
303: /**
304: * Adjust the internal arrays for the requested number of points.
305: *
306: * @param nParams the number of points to add to the array.
307: */
308: void newCommand(final int nPoints) {
309: // Adjust the length of the command array if needed.
310: if (nSegments == commands.length) {
311: byte[] tmpCommands = new byte[commands.length * 2 + 1];
312: System.arraycopy(commands, 0, tmpCommands, 0,
313: commands.length);
314: commands = tmpCommands;
315: }
316:
317: // Adjust the length of the data array if needed.
318: if (nData + nPoints * 2 > data.length) {
319: float[] tmpData = new float[(nData + nPoints * 2) * 2];
320: System.arraycopy(data, 0, tmpData, 0, data.length);
321: data = tmpData;
322: }
323: }
324:
325: /**
326: *
327: */
328: public void moveTo(final float x, final float y) {
329: newCommand(1);
330: commands[nSegments] = MOVE_TO_IMPL;
331: data[nData] = x;
332: data[nData + 1] = y;
333:
334: nSegments++;
335: nData += 2;
336: }
337:
338: /**
339: *
340: */
341: public void lineTo(final float x, final float y) {
342: newCommand(1);
343: commands[nSegments] = LINE_TO_IMPL;
344: data[nData] = x;
345: data[nData + 1] = y;
346:
347: nSegments++;
348: nData += 2;
349: }
350:
351: /**
352: *
353: */
354: public void quadTo(final float x1, final float y1, final float x2,
355: final float y2) {
356: newCommand(2);
357: commands[nSegments] = QUAD_TO_IMPL;
358: data[nData] = x1;
359: data[nData + 1] = y1;
360: data[nData + 2] = x2;
361: data[nData + 3] = y2;
362:
363: nSegments++;
364: nData += 4;
365: }
366:
367: /**
368: *
369: */
370: public void curveTo(final float x1, final float y1, final float x2,
371: final float y2, final float x3, final float y3) {
372: newCommand(3);
373: commands[nSegments] = CURVE_TO_IMPL;
374:
375: data[nData] = x1;
376: data[nData + 1] = y1;
377: data[nData + 2] = x2;
378: data[nData + 3] = y2;
379: data[nData + 4] = x3;
380: data[nData + 5] = y3;
381:
382: nSegments++;
383: nData += 6;
384: }
385:
386: /**
387: *
388: */
389: public void close() {
390: newCommand(0);
391: commands[nSegments] = CLOSE_IMPL;
392:
393: nSegments++;
394: }
395:
396: /**
397: * @return a String representation of this path, using the SVG notation.
398: */
399: public String toString() {
400: return toString(data);
401: }
402:
403: /**
404: * @param d the set of data values to use for the string conversion. This
405: * will fail if the input data array does not have at least nData
406: * entries of at least 2 values.
407: * @return a String representation of this path, using the SVG notation.
408: */
409: public String toString(final float[] d) {
410: StringBuffer sb = new StringBuffer();
411: int offset = 0;
412: for (int i = 0; i < nSegments; i++) {
413: switch (commands[i]) {
414: case Path.MOVE_TO_IMPL:
415: sb.append('M');
416: sb.append(d[offset]);
417: sb.append(',');
418: sb.append(d[offset + 1]);
419: offset += 2;
420: break;
421: case Path.LINE_TO_IMPL:
422: sb.append('L');
423: sb.append(d[offset]);
424: sb.append(',');
425: sb.append(d[offset + 1]);
426: offset += 2;
427: break;
428: case Path.QUAD_TO_IMPL:
429: sb.append('Q');
430: sb.append(d[offset]);
431: sb.append(',');
432: sb.append(d[offset + 1]);
433: sb.append(',');
434: sb.append(d[offset + 2]);
435: sb.append(',');
436: sb.append(d[offset + 3]);
437: offset += 4;
438: break;
439: case Path.CURVE_TO_IMPL:
440: sb.append('C');
441: sb.append(d[offset]);
442: sb.append(',');
443: sb.append(d[offset + 1]);
444: sb.append(',');
445: sb.append(d[offset + 2]);
446: sb.append(',');
447: sb.append(d[offset + 3]);
448: sb.append(',');
449: sb.append(d[offset + 4]);
450: sb.append(',');
451: sb.append(d[offset + 5]);
452: offset += 6;
453: break;
454: case Path.CLOSE_IMPL:
455: sb.append('Z');
456: break;
457: default:
458: throw new Error();
459: }
460: }
461:
462: return sb.toString();
463: }
464:
465: /**
466: * @return a string descriton of this Path using the points syntax. This
467: * methods throws an IllegalStateException if the path does not have the
468: * commands corresponding to the points syntax (initial move to followed
469: * by linetos and closes
470: */
471: public String toPointsString() {
472: return toPointsString(data);
473: }
474:
475: /**
476: * @param d the set of data values to use for the string conversion. This
477: * will fail if the input data array does not have an even number of values.
478: * @return a string descriton of this Path using the points syntax. This
479: * methods throws an IllegalStateException if the path does not have the
480: * commands corresponding to the points syntax (initial move to followed by
481: * linetos and closes
482: */
483: public String toPointsString(final float[] d) {
484: StringBuffer sb = new StringBuffer();
485: int curSegment = 0;
486: int off = 0, cmd = 0;
487:
488: while (curSegment < nSegments) {
489: switch (commands[curSegment]) {
490: case Path.MOVE_TO_IMPL:
491: if (curSegment > 0) {
492: throw new IllegalArgumentException();
493: }
494: sb.append(d[off]);
495: sb.append(',');
496: sb.append(d[off + 1]);
497: sb.append(' ');
498: off += 2;
499: break;
500: case Path.LINE_TO_IMPL:
501: sb.append(d[off]);
502: sb.append(',');
503: sb.append(d[off + 1]);
504: sb.append(' ');
505: off += 2;
506: break;
507: case Path.CLOSE_IMPL:
508: break;
509: default:
510: case Path.QUAD_TO_IMPL:
511: case Path.CURVE_TO_IMPL:
512: throw new IllegalArgumentException();
513: }
514:
515: curSegment++;
516: }
517: return sb.toString().trim();
518: }
519:
520: /**
521: * Compute the path's bounds.
522: *
523: * @return the path's bounds.
524: */
525: public Box getBounds() {
526: float x1, y1, x2, y2;
527: int i = nData - 2;
528: if (nData > 0) {
529: x1 = x2 = data[i];
530: y1 = y2 = data[i + 1];
531: i -= 2;
532: while (i >= 0) {
533: float x = data[i];
534: float y = data[i + 1];
535: i -= 2;
536: if (x < x1)
537: x1 = x;
538: if (y < y1)
539: y1 = y;
540: if (x > x2)
541: x2 = x;
542: if (y > y2)
543: y2 = y;
544: }
545: } else {
546: x1 = y1 = x2 = y2 = 0.0f;
547: }
548:
549: return new Box(x1, y1, x2 - x1, y2 - y1);
550: }
551:
552: /**
553: * Compute the path's bounds in the transformed coordinate system.
554: */
555: public Box getTransformedBounds(final Transform t) {
556: float x1, y1, x2, y2;
557: int i = nData - 2;
558: float x0, y0, x, y;
559: if (nData > 0) {
560: x0 = data[i];
561: y0 = data[i + 1];
562: x1 = x2 = x0 * t.m0 + y0 * t.m2 + t.m4;
563: y1 = y2 = x0 * t.m1 + y0 * t.m3 + t.m5;
564: i -= 2;
565: while (i >= 0) {
566: x0 = data[i];
567: y0 = data[i + 1];
568: i -= 2;
569: x = x0 * t.m0 + y0 * t.m2 + t.m4;
570: y = x0 * t.m1 + y0 * t.m3 + t.m5;
571: if (x < x1)
572: x1 = x;
573: if (y < y1)
574: y1 = y;
575: if (x > x2)
576: x2 = x;
577: if (y > y2)
578: y2 = y;
579: }
580: } else {
581: x1 = y1 = x2 = y2 = 0.0f;
582: }
583:
584: return new Box(x1, y1, x2 - x1, y2 - y1);
585: }
586:
587: /**
588: * @return true if obj is a path and all its commands are the
589: * same as this instance.
590: */
591: public boolean equals(final Object obj) {
592: if (obj == this ) {
593: return true;
594: }
595:
596: if (obj == null || !(obj instanceof Path)) {
597: return false;
598: }
599:
600: Path p = (Path) obj;
601: if (nSegments != p.nSegments || nData != nData) {
602: return false;
603: }
604:
605: // Compare each command type and offset
606: for (int ci = 0; ci < nSegments; ci++) {
607: // Check we are dealing with the same command type
608: if (commands[ci] != p.commands[ci]) {
609: return false;
610: }
611: }
612:
613: // Now, compare each command data
614: for (int di = 0; di < nData; di++) {
615: if (data[di] != p.data[di]) {
616: return false;
617: }
618: }
619:
620: return true;
621: }
622: }
|