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.svg.SVGPath;
029:
030: import com.sun.pisces.Dasher;
031: import com.sun.pisces.Flattener;
032: import com.sun.pisces.PathSink;
033: import com.sun.pisces.PathStore;
034: import com.sun.pisces.Stroker;
035: import com.sun.pisces.Transformer;
036: import com.sun.pisces.Transform4;
037: import com.sun.pisces.Transform6;
038:
039: /**
040: * @version $Id: PathSupport.java,v 1.12 2006/04/21 06:35:29 st125089 Exp $
041: */
042: public class PathSupport {
043: /**
044: * Identity Transform4
045: */
046: private static final Transform4 identity = new Transform4();
047:
048: /**
049: * Identity Transform6
050: */
051: private static final Transform6 identity6 = new Transform6();
052:
053: /**
054: * A transform used in the implementation of computeStrokedPathTile
055: */
056: private static final Transform6 txf = new Transform6();
057:
058: /**
059: * Used to compute a stroked path's outline.
060: */
061: private static TileSink tileSink = new TileSink();
062:
063: /**
064: * Used to transform the coordinates of stroked path.
065: */
066: private static Transformer transformerSink = new Transformer(
067: tileSink, identity6);
068:
069: private static int toFixed(float f) {
070: return (int) (f * 65536.0f);
071: }
072:
073: private static void emitPath(Path path, PathSink output) {
074: int numSegments = path.getNumberOfSegments();
075: byte[] pathCommands = path.getCommands();
076: float[] pathData = path.getData();
077:
078: int x1 = -1, y1 = -1, x2, y2, x3, y3;
079: float[] pt = null;
080:
081: int offset = 0;
082:
083: for (int seg = 0; seg < numSegments; seg++) {
084: if (pathCommands[seg] != Path.CLOSE_IMPL) {
085: x1 = toFixed(pathData[offset]);
086: y1 = toFixed(pathData[offset + 1]);
087: }
088:
089: switch (pathCommands[seg]) {
090: case Path.MOVE_TO_IMPL:
091: output.moveTo(x1, y1);
092: offset += 2;
093: break;
094: case Path.LINE_TO_IMPL:
095: output.lineTo(x1, y1);
096: offset += 2;
097: break;
098: case Path.QUAD_TO_IMPL:
099: x2 = toFixed(pathData[offset + 2]);
100: y2 = toFixed(pathData[offset + 3]);
101: output.quadTo(x1, y1, x2, y2);
102: offset += 4;
103: break;
104: case Path.CURVE_TO_IMPL:
105: x2 = toFixed(pathData[offset + 2]);
106: y2 = toFixed(pathData[offset + 3]);
107: x3 = toFixed(pathData[offset + 4]);
108: y3 = toFixed(pathData[offset + 5]);
109: output.cubicTo(x1, y1, x2, y2, x3, y3);
110: offset += 6;
111: break;
112: case Path.CLOSE_IMPL:
113: output.close();
114: break;
115: }
116: }
117:
118: output.end();
119: }
120:
121: /**
122: * Returns true if the shape is hit by the given point.
123: *
124: * @param path the shape on which we do hit testing. Should not be null.
125: * @param windingRule the winding rule fo the path.
126: * @param hx the hit point x-axis coordinate.
127: * @param hy the hit point y-axis coordinate.
128: *
129: * @return true if the input shape is hit. false otherwise.
130: */
131: public static boolean isHit(final Path path, final int windingRule,
132: final float hx, final float hy) {
133: HitTester hitTester = new HitTester(windingRule, toFixed(hx),
134: toFixed(hy));
135: emitPath(path, hitTester);
136: boolean hit = hitTester.containsPoint();
137: return hit;
138: }
139:
140: /**
141: * Returns true if the input object is hit by the given point.
142: *
143: * @param strokedPath the shape on which we do hit testing.
144: * Should not be null.
145: * @param windingRule the winding rule fo the path.
146: * @param hx the hit point x-axis coordinate.
147: * @param hy the hit point y-axis coordinate.
148: *
149: * @return true if the input shape is hit. false otherwise.
150: */
151: public static boolean isStrokedPathHit(final Object strokedPath,
152: final int windingRule, final float hx, final float hy) {
153: HitTester hitTester = new HitTester(Path.WIND_NON_ZERO,
154: toFixed(hx), toFixed(hy));
155: PathStore path = (PathStore) strokedPath;
156: path.produce(hitTester);
157: boolean hit = hitTester.containsPoint();
158: return hit;
159: }
160:
161: /**
162: * @param strokedPath the object returned from a previous getStrokedPath
163: * call. Should not be null.
164: * @param t the transform from the strokedPath space to the requested
165: * tile space.
166: * @param tile the bounds of the given stroked outline.
167: */
168: public static void computeStrokedPathTile(final Tile tile,
169: final Object strokedPath, final Transform t) {
170: // Reuse the same TileSink over and over again.
171: PathStore sp = (PathStore) strokedPath;
172:
173: // We use out own TileSink, chained with a Transformer to compute the
174: // stroked shape bounds.
175:
176: // Start from initial values
177: tileSink.reset();
178:
179: // Transfer the input Transform value to the working transform txf.
180: RenderGraphics.setTransform(t, txf);
181:
182: // Compute now. This call starts pulling data.
183: transformerSink.setTransform(txf);
184: sp.produce(transformerSink);
185:
186: // Compute the tile from the data that was just computed.
187: tileSink.setTile(tile);
188: }
189:
190: /**
191: * Returns a PathSink that will accept path commands and feed them
192: * into a stroking pipeline based on the stroke parameters of a
193: * given GraphicsProperties. The stroked output will be fed into the
194: * given output PathSink.
195: *
196: * @param output a PathSink that will receive the stroked outline
197: * @param gp a GraphicsProperties containing stroking parameters
198: * @return a new PathSink that can accept the path to be stroked
199: */
200: private static PathSink createStroker(PathSink output,
201: final GraphicsProperties gp) {
202: Stroker stroker = new Stroker();
203: stroker.setParameters((int) (gp.getStrokeWidth() * 65536), gp
204: .getStrokeLineCap(), gp.getStrokeLineJoin(), (int) (gp
205: .getStrokeMiterLimit() * 65536), identity);
206: stroker.setOutput(output);
207: Flattener flattener = new Flattener();
208: flattener.setFlatness(1 << 16);
209:
210: float[] strokeDashArray = gp.getStrokeDashArray();
211: if (strokeDashArray != null) {
212: int[] intStrokeDashArray = new int[strokeDashArray.length];
213: for (int i = 0; i < strokeDashArray.length; i++) {
214: intStrokeDashArray[i] = (int) (strokeDashArray[i] * 65536);
215: }
216:
217: // flattener -> dasher -> stroker -> output
218: Dasher dasher = new Dasher();
219: dasher.setParameters(intStrokeDashArray,
220: computeStrokeDashOffset(gp.getStrokeDashOffset(),
221: gp.getStrokeDashArray()), identity);
222: dasher.setOutput(stroker);
223: flattener.setOutput(dasher);
224: } else {
225: // flattener -> stroker -> output
226: flattener.setOutput(stroker);
227: }
228:
229: return flattener;
230: }
231:
232: /**
233: * Implemnetation: Handles negative strokeDashOffset
234: *
235: * @param strokeDashOffset the possibly negative dash offset value.
236: * @param strokeDashArray the applicable dash array.
237: * @return a positive strokeDashOffset.
238: */
239: static int computeStrokeDashOffset(final float strokeDashOffset,
240: final float[] strokeDashArray) {
241: if (strokeDashArray == null) {
242: // The stroke dash offset does not matter, simply return 0
243: return 0;
244: }
245:
246: if (strokeDashOffset >= 0) {
247: return (int) (strokeDashOffset * 65536);
248: }
249:
250: int length = 0;
251: for (int i = 0; i < strokeDashArray.length; i++) {
252: length += strokeDashArray[i];
253: }
254:
255: if (length <= 0) {
256: return 0;
257: }
258:
259: float sdo = strokeDashOffset;
260: while (sdo < 0) {
261: sdo += length;
262: }
263:
264: return (int) (sdo * 65536);
265: }
266:
267: /**
268: * @param path the Path to stroke.
269: * @param gp the GraphicsProperties defining the rendering conditions.
270: *
271: * @return the stroked outline.
272: */
273: public static Object getStrokedPath(final Path path,
274: final GraphicsProperties gp) {
275: PathStore strokedPath = new PathStore();
276: PathSink stroker = createStroker(strokedPath, gp);
277:
278: emitPath(path, stroker);
279:
280: return strokedPath;
281: }
282:
283: /**
284: * @param x the rectangle's x-axis origin
285: * @param y the rectangle's y-axis origin
286: * @param w the rectangle's length along the x-axis
287: * @param h the rectangle's length along the y-axis
288: * @param gp the GraphicsProperties defining rendering conditions.
289: *
290: * @return the stroked outline.
291: */
292: public static Object getStrokedRect(final float x, final float y,
293: final float w, final float h, final GraphicsProperties gp) {
294: PathStore strokedPath = new PathStore();
295: PathSink stroker = createStroker(strokedPath, gp);
296:
297: stroker.moveTo(toFixed(x), toFixed(y));
298: stroker.lineTo(toFixed(x + w), toFixed(y));
299: stroker.lineTo(toFixed(x + w), toFixed(y + h));
300: stroker.lineTo(toFixed(x), toFixed(y + h));
301: stroker.close();
302: stroker.end();
303:
304: return strokedPath;
305: }
306:
307: private static long acv = (long) (65536.0 * 0.22385762508460333);
308:
309: /**
310: * @param fx the rectangle's x-axis origin
311: * @param fy the rectangle's y-axis origin
312: * @param fw the rectangle's length along the x-axis
313: * @param fh the rectangle's length along the y-axis
314: * @param rx the rectangle's rounded corner diameter along the x-axis
315: * @param ry the rectangle's rounded corner diameter along the y-axis.
316: * @param gp the GraphicsProperties defining rendering conditions.
317: *
318: * @return the stroked outline.
319: */
320: public static Object getStrokedRect(final float fx, final float fy,
321: final float fw, final float fh, final float rx,
322: final float ry, final GraphicsProperties gp) {
323: int x = toFixed(fx);
324: int y = toFixed(fy);
325: int w = toFixed(fw);
326: int h = toFixed(fh);
327: int aw = toFixed(rx);
328: int ah = toFixed(ry);
329:
330: int xw = x + w;
331: int yh = y + h;
332: int aw2 = aw >> 1;
333: int ah2 = ah >> 1;
334: int acvaw = (int) (acv * aw >> 16);
335: int acvah = (int) (acv * ah >> 16);
336: int xacvaw = x + acvaw;
337: int xw_acvaw = xw - acvaw;
338: int yacvah = y + acvah;
339: int yh_acvah = yh - acvah;
340: int xaw2 = x + aw2;
341: int xw_aw2 = xw - aw2;
342: int yah2 = y + ah2;
343: int yh_ah2 = yh - ah2;
344:
345: PathStore strokedPath = new PathStore();
346: PathSink stroker = createStroker(strokedPath, gp);
347:
348: stroker.moveTo(x, yah2);
349: stroker.lineTo(x, yh_ah2);
350: stroker.cubicTo(x, yh_acvah, xacvaw, yh, xaw2, yh);
351: stroker.lineTo(xw_aw2, yh);
352: stroker.cubicTo(xw_acvaw, yh, xw, yh_acvah, xw, yh_ah2);
353: stroker.lineTo(xw, yah2);
354: stroker.cubicTo(xw, yacvah, xw_acvaw, y, xw_aw2, y);
355: stroker.lineTo(xaw2, y);
356: stroker.cubicTo(xacvaw, y, x, yacvah, x, yah2);
357: stroker.close();
358: stroker.end();
359:
360: return strokedPath;
361: }
362:
363: /**
364: * @param x1 the line's x-axis starting position.
365: * @param y1 the line's y-axis starting position.
366: * @param x2 the line's x-axis end position.
367: * @param y2 the line's y-axis end position.
368: * @param gp the GraphicsProperties defining rendering conditions.
369: *
370: * @return the stroked outline.
371: */
372: public static Object getStrokedLine(final float x1, final float y1,
373: final float x2, final float y2, final GraphicsProperties gp) {
374: PathStore strokedPath = new PathStore();
375: PathSink stroker = createStroker(strokedPath, gp);
376:
377: stroker.moveTo(toFixed(x1), toFixed(y1));
378: stroker.lineTo(toFixed(x2), toFixed(y2));
379: stroker.end();
380:
381: return strokedPath;
382: }
383:
384: private static final double CtrlVal = 0.5522847498307933;
385: private static long pcv_ = (long) (65536.0 * (0.5 + CtrlVal * 0.5));
386: private static long ncv_ = (long) (65536.0 * (0.5 - CtrlVal * 0.5));
387:
388: /**
389: * @param x the ellipse's x-axis origin
390: * @param y the ellipse's y-axis origin
391: * @param width the ellipse's x-axis length
392: * @param height the ellipse's y-axis length.
393: * @param gp the GraphicsProperties defining rendering conditions.
394: *
395: * @return the stroked outline.
396: */
397: public static Object getStrokedEllipse(final float x,
398: final float y, final float width, final float height,
399: final GraphicsProperties gp) {
400:
401: PathStore strokedPath = new PathStore();
402: PathSink stroker = createStroker(strokedPath, gp);
403:
404: int x_ = toFixed(x);
405: int y_ = toFixed(y);
406: int width_ = toFixed(width);
407: int height_ = toFixed(height);
408:
409: int xw_ = x_ + width_;
410: int xw2_ = x_ + (width_ >> 1);
411: int yh_ = y_ + height_;
412: int yh2_ = y_ + (height_ >> 1);
413: int xpcvw_ = x_ + (int) (pcv_ * width_ >> 16);
414: int ypcvh_ = y_ + (int) (pcv_ * height_ >> 16);
415: int xncvw_ = x_ + (int) (ncv_ * width_ >> 16);
416: int yncvh_ = y_ + (int) (ncv_ * height_ >> 16);
417:
418: stroker.moveTo(xw_, yh2_);
419: stroker.cubicTo(xw_, ypcvh_, xpcvw_, yh_, xw2_, yh_);
420: stroker.cubicTo(xncvw_, yh_, x_, ypcvh_, x_, yh2_);
421: stroker.cubicTo(x_, yncvh_, xncvw_, y_, xw2_, y_);
422: stroker.cubicTo(xpcvw_, y_, xw_, yncvh_, xw_, yh2_);
423: stroker.close();
424: stroker.end();
425:
426: return strokedPath;
427: }
428: }
429:
430: class HitTester extends PathSink {
431:
432: int windingRule;
433: int px, py;
434: int x0, y0, sx0, sy0;
435:
436: int crossings = 0;
437:
438: public HitTester(int windingRule, int px, int py) {
439: this .windingRule = windingRule;
440: this .px = px;
441: this .py = py;
442: }
443:
444: public void moveTo(int x0, int y0) {
445: this .x0 = this .sx0 = x0;
446: this .y0 = this .sy0 = y0;
447: }
448:
449: public void lineJoin() {
450: }
451:
452: public void lineTo(int x1, int y1) {
453: int orientation;
454: int hity;
455:
456: int dy = y1 - y0;
457: if (dy > 0) {
458: orientation = 1;
459: hity = py - y0;
460: } else {
461: orientation = -1;
462: hity = py - y1;
463: dy = -dy;
464: }
465:
466: // If line's Y extent includes py, find the X value where the line
467: // intersects the horizontal line y = py. If the intersection lies
468: // to the left of px, accumulate the line orientation into the
469: // count of crossings.
470:
471: if (hity >= 0 && hity < dy) {
472: int hitx, dx;
473:
474: if (orientation == 1) {
475: hitx = px - x0;
476: dx = x1 - x0;
477: } else {
478: hitx = px - x1;
479: dx = x0 - x1;
480: }
481:
482: if ((long) hity * dx < (long) hitx * dy) {
483: crossings += orientation;
484: }
485: }
486:
487: this .x0 = x1;
488: this .y0 = y1;
489: }
490:
491: public void quadTo(int x1, int y1, int x2, int y2) {
492: System.err
493: .println(">>>>>>>>>>>> path for hit testing should not contain quadTo!");
494: }
495:
496: public void cubicTo(int x1, int y1, int x2, int y2, int x3, int y3) {
497: System.err
498: .println(">>>>>>>>>>>> path for hit testing should not contain cubicTo!");
499: }
500:
501: public void close() {
502: lineTo(sx0, sy0);
503: }
504:
505: public void end() {
506: }
507:
508: public boolean containsPoint() {
509: return (windingRule == Path.WIND_NON_ZERO) ? (crossings != 0)
510: : ((crossings & 0x1) == 0x1);
511: }
512: }
|