// Copyright 2000-2005 the Contributors, as shown in the revision logs. // Licensed under the GNU General Public License version 2 ("the License"). // You may not use this file except in compliance with the License. package org.ibex.graphics; import java.util.*; import org.ibex.util.*; /** an abstract path; may contain splines and arcs */ public class Path { public static final float PX_PER_INCH = 72; public static final float INCHES_PER_CM = (float)0.3937; public static final float INCHES_PER_MM = INCHES_PER_CM / 10; private static final int DEFAULT_PATHLEN = 1000; private static final int NUMSTEPS = 10; private static final float PI = (float)Math.PI; boolean closed = false; Curve head = null; Curve tail = null; protected void add(Curve c) { if (head==null) { tail=head=c; return; } c.prev = tail; tail.next = c; tail = c; } public void addTo(Mesh m, boolean evenOdd) { for(Curve c = head; c != null; c = c.next) c.addTo(m); m.setIn(evenOdd); } abstract class Curve { Curve next, prev; float x, y; float c1x, c1y, c2x, c2y; public Curve() { } public abstract void addTo(Mesh ret); } class Line extends Curve { public void addTo(Mesh ret) { float rx = next.x; float ry = next.y; ret.add(rx,ry); } } class Move extends Curve { public void addTo(Mesh ret) { ret.newcontour(); if (next==null) return; float rx = next.x; float ry = next.y; ret.add(rx, ry); } } class Arc extends Curve { public void addTo(Mesh ret) { System.out.println("ARC!"); float rx = c1x; float ry = c1y; float phi = c2x; float fa = ((int)c2y) >> 1; float fs = ((int)c2y) & 1; float x1 = x; float y1 = y; float x2 = next.x; float y2 = next.y; // F.6.5: given x1,y1,x2,y2,fa,fs, compute cx,cy,theta1,dtheta float x1_ = (float)Math.cos(phi) * (x1 - x2) / 2 + (float)Math.sin(phi) * (y1 - y2) / 2; float y1_ = -1 * (float)Math.sin(phi) * (x1 - x2) / 2 + (float)Math.cos(phi) * (y1 - y2) / 2; float tmp = (float)Math.sqrt((rx * rx * ry * ry - rx * rx * y1_ * y1_ - ry * ry * x1_ * x1_) / (rx * rx * y1_ * y1_ + ry * ry * x1_ * x1_)); float cx_ = (fa == fs ? -1 : 1) * tmp * (rx * y1_ / ry); float cy_ = (fa == fs ? -1 : 1) * -1 * tmp * (ry * x1_ / rx); float cx = (float)Math.cos(phi) * cx_ - (float)Math.sin(phi) * cy_ + (x1 + x2) / 2; float cy = (float)Math.sin(phi) * cx_ + (float)Math.cos(phi) * cy_ + (y1 + y2) / 2; // F.6.4 Conversion from center to endpoint parameterization float ux = 1, uy = 0, vx = (x1_ - cx_) / rx, vy = (y1_ - cy_) / ry; float det = ux * vy - uy * vx; float theta1 = (det < 0 ? -1 : 1) * (float)Math.acos((ux * vx + uy * vy) / ((float)Math.sqrt(ux * ux + uy * uy) * (float)Math.sqrt(vx * vx + vy * vy))); ux = (x1_ - cx_) / rx; uy = (y1_ - cy_) / ry; vx = (-1 * x1_ - cx_) / rx; vy = (-1 * y1_ - cy_) / ry; det = ux * vy - uy * vx; float dtheta = (det < 0 ? -1 : 1) * (float)Math.acos((ux * vx + uy * vy) / ((float)Math.sqrt(ux * ux + uy * uy) * (float)Math.sqrt(vx * vx + vy * vy))); dtheta = dtheta % (float)(2 * Math.PI); if (fs == 0 && dtheta > 0) theta1 -= 2 * PI; if (fs == 1 && dtheta < 0) theta1 += 2 * PI; if (fa == 1 && dtheta < 0) dtheta = 2 * PI + dtheta; else if (fa == 1 && dtheta > 0) dtheta = -1 * (2 * PI - dtheta); // FIXME: integrate F.6.6 // FIXME: isn't quite ending where it should... // F.6.3: Parameterization alternatives float theta = theta1; for(int j=0; jmaxx) maxx = x; if (xmaxy) maxy = y; if (ymaxx) maxx = c1x; if (c1xmaxy) maxy = c1y; if (c1ymaxx) maxx = c2x; if (c2xmaxy) maxy = c2y; if (c2y= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' || c == '-')) { if (c == '%') { // FIXME } else if (s.regionMatches(i, "pt", 0, i+2)) { // FIXME } else if (s.regionMatches(i, "em", 0, i+2)) { // FIXME } else if (s.regionMatches(i, "pc", 0, i+2)) { // FIXME } else if (s.regionMatches(i, "ex", 0, i+2)) { // FIXME } else if (s.regionMatches(i, "mm", 0, i+2)) { i += 2; multiplier = INCHES_PER_MM * PX_PER_INCH; break; } else if (s.regionMatches(i, "cm", 0, i+2)) { i += 2; multiplier = INCHES_PER_CM * PX_PER_INCH; break; } else if (s.regionMatches(i, "in", 0, i+2)) { i += 2; multiplier = PX_PER_INCH; break; } else if (s.regionMatches(i, "px", 0, i+2)) { i += 2; break; } else if (Character.isLetter(c)) break; throw new RuntimeException("didn't expect character \"" + c + "\" in a numeric constant"); } } //if (start == i) throw new RuntimeException("FIXME"); if (start == i) return (float)0.0; try { return Float.parseFloat(s.substring(start, i)) * multiplier; } catch (NumberFormatException nfe) { Log.warn(Path.class, "offending string was \"" + s.substring(start, i) + "\""); throw nfe; } } } protected void parseSingleCommandAndArguments(Tokenizer t, char command, boolean relative) { if (tail==null && command!='m') throw new RuntimeException("first command MUST be an 'm', not a " + command); switch(command) { case 'z': { Curve c; for(c = tail.prev; c != null && !(c instanceof Move); c = c.prev); Line ret = new Line(); ret.x = c.x; ret.y = c.y; add(ret); Move mov = new Move(); mov.x = ret.x; mov.y = ret.y; add(mov); closed = true; // FIXME: actually, we should search back to the last 'z' or 'm', not just 'm' break; } case 'm': { // feature: collapse consecutive movetos Move ret = new Move(); ret.x = t.parseFloat() + (relative ? tail.y : 0); ret.y = t.parseFloat() + (relative ? tail.y : 0); add(ret); break; } case 'l': case 'h': case 'v': { float first = t.parseFloat(), second; if (command == 'h') second = relative ? 0 : tail.y; else if (command == 'v') { second = first; first = relative ? 0 : tail.x; } else second = t.parseFloat(); Line ret = new Line(); ret.x = first + (relative ? tail.x : 0); ret.y = second + (relative ? tail.y : 0); add(ret); break; } case 'a': { Arc ret = new Arc(); ret.c1x = t.parseFloat() + (relative ? tail.x : 0); ret.c1y = t.parseFloat() + (relative ? tail.y : 0); ret.c2x = (t.parseFloat() / 360) * 2 * PI; ret.c2y = t.parseFloat(); ret.x = t.parseFloat() + (relative ? tail.x : 0); ret.y = t.parseFloat() + (relative ? tail.y : 0); add(ret); break; } case 's': case 'c': { Bezier ret = new Bezier(); if (command == 'c') { tail.c1x = t.parseFloat() + (relative ? tail.x : 0); tail.c1y = t.parseFloat() + (relative ? tail.y : 0); } else if (head != null && tail instanceof Bezier) { tail.c1x = 2 * tail.x-((Bezier)tail).c2x; tail.c1y = 2 * tail.y-((Bezier)tail).c2x; } else { tail.c1x = tail.x; tail.c1y = tail.y; } tail.c2x = t.parseFloat() + (relative ? tail.x : 0); tail.c2y = t.parseFloat() + (relative ? tail.y : 0); ret.x = t.parseFloat() + (relative ? tail.x : 0); ret.y = t.parseFloat() + (relative ? tail.y : 0); add(ret); break; } case 't': case 'q': { QuadBezier ret = new QuadBezier(); if (command == 'q') { tail.c1x = t.parseFloat() + (relative ? tail.x : 0); tail.c1y = t.parseFloat() + (relative ? tail.y : 0); } else if (head != null && tail instanceof QuadBezier) { tail.c1x = 2 * tail.x-((QuadBezier)tail).c1x; tail.c1y = 2 * tail.y-((QuadBezier)tail).c1y; } else { tail.c1x = tail.x; tail.c1y = tail.y; } ret.x = t.parseFloat() + (relative ? tail.x : 0); ret.y = t.parseFloat() + (relative ? tail.y : 0); add(ret); break; } default: } } }