1. /*
  2. * @(#)Map.java 1.8 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text.html;
  8. import java.awt.Polygon;
  9. import java.awt.Rectangle;
  10. import java.util.StringTokenizer;
  11. import java.util.Vector;
  12. import javax.swing.text.AttributeSet;
  13. /**
  14. * Map is used to represent a map element that is part of an HTML document.
  15. * Once a Map has been created, and any number of areas have been added,
  16. * you can test if a point falls inside the map via the contains method.
  17. *
  18. * @author Scott Violet
  19. * @version 1.8 12/19/03
  20. */
  21. class Map {
  22. /** Name of the Map. */
  23. private String name;
  24. /** An array of AttributeSets. */
  25. private Vector areaAttributes;
  26. /** An array of RegionContainments, will slowly grow to match the
  27. * length of areaAttributes as needed. */
  28. private Vector areas;
  29. public Map() {
  30. }
  31. public Map(String name) {
  32. this.name = name;
  33. }
  34. /**
  35. * Returns the name of the Map.
  36. */
  37. public String getName() {
  38. return name;
  39. }
  40. /**
  41. * Defines a region of the Map, based on the passed in AttributeSet.
  42. */
  43. public void addArea(AttributeSet as) {
  44. if (as == null) {
  45. return;
  46. }
  47. if (areaAttributes == null) {
  48. areaAttributes = new Vector(2);
  49. }
  50. areaAttributes.addElement(as.copyAttributes());
  51. }
  52. /**
  53. * Removes the previously created area.
  54. */
  55. public void removeArea(AttributeSet as) {
  56. if (as != null && areaAttributes != null) {
  57. int numAreas = (areas != null) ? areas.size() : 0;
  58. for (int counter = areaAttributes.size() - 1; counter >= 0;
  59. counter--) {
  60. if (((AttributeSet)areaAttributes.elementAt(counter)).
  61. isEqual(as)){
  62. areaAttributes.removeElementAt(counter);
  63. if (counter < numAreas) {
  64. areas.removeElementAt(counter);
  65. }
  66. }
  67. }
  68. }
  69. }
  70. /**
  71. * Returns the AttributeSets representing the differet areas of the Map.
  72. */
  73. public AttributeSet[] getAreas() {
  74. int numAttributes = (areaAttributes != null) ? areaAttributes.size() :
  75. 0;
  76. if (numAttributes != 0) {
  77. AttributeSet[] retValue = new AttributeSet[numAttributes];
  78. areaAttributes.copyInto(retValue);
  79. return retValue;
  80. }
  81. return null;
  82. }
  83. /**
  84. * Returns the AttributeSet that contains the passed in location,
  85. * <code>x</code>, <code>y</code>. <code>width</code>, <code>height</code>
  86. * gives the size of the region the map is defined over. If a matching
  87. * area is found, the AttribueSet for it is returned.
  88. */
  89. public AttributeSet getArea(int x, int y, int width, int height) {
  90. int numAttributes = (areaAttributes != null) ?
  91. areaAttributes.size() : 0;
  92. if (numAttributes > 0) {
  93. int numAreas = (areas != null) ? areas.size() : 0;
  94. if (areas == null) {
  95. areas = new Vector(numAttributes);
  96. }
  97. for (int counter = 0; counter < numAttributes; counter++) {
  98. if (counter >= numAreas) {
  99. areas.addElement(createRegionContainment
  100. ((AttributeSet)areaAttributes.elementAt(counter)));
  101. }
  102. RegionContainment rc = (RegionContainment)areas.
  103. elementAt(counter);
  104. if (rc != null && rc.contains(x, y, width, height)) {
  105. return (AttributeSet)areaAttributes.elementAt(counter);
  106. }
  107. }
  108. }
  109. return null;
  110. }
  111. /**
  112. * Creates and returns an instance of RegionContainment that can be
  113. * used to test if a particular point lies inside a region.
  114. */
  115. protected RegionContainment createRegionContainment
  116. (AttributeSet attributes) {
  117. Object shape = attributes.getAttribute(HTML.Attribute.SHAPE);
  118. if (shape == null) {
  119. shape = "rect";
  120. }
  121. if (shape instanceof String) {
  122. String shapeString = ((String)shape).toLowerCase();
  123. RegionContainment rc = null;
  124. try {
  125. if (shapeString.equals("rect")) {
  126. rc = new RectangleRegionContainment(attributes);
  127. }
  128. else if (shapeString.equals("circle")) {
  129. rc = new CircleRegionContainment(attributes);
  130. }
  131. else if (shapeString.equals("poly")) {
  132. rc = new PolygonRegionContainment(attributes);
  133. }
  134. else if (shapeString.equals("default")) {
  135. rc = DefaultRegionContainment.sharedInstance();
  136. }
  137. } catch (RuntimeException re) {
  138. // Something wrong with attributes.
  139. rc = null;
  140. }
  141. return rc;
  142. }
  143. return null;
  144. }
  145. /**
  146. * Creates and returns an array of integers from the String
  147. * <code>stringCoords</code>. If one of the values represents a
  148. * % the returned value with be negative. If a parse error results
  149. * from trying to parse one of the numbers null is returned.
  150. */
  151. static protected int[] extractCoords(Object stringCoords) {
  152. if (stringCoords == null || !(stringCoords instanceof String)) {
  153. return null;
  154. }
  155. StringTokenizer st = new StringTokenizer((String)stringCoords,
  156. ", \t\n\r");
  157. int[] retValue = null;
  158. int numCoords = 0;
  159. while(st.hasMoreElements()) {
  160. String token = st.nextToken();
  161. int scale;
  162. if (token.endsWith("%")) {
  163. scale = -1;
  164. token = token.substring(0, token.length() - 1);
  165. }
  166. else {
  167. scale = 1;
  168. }
  169. try {
  170. int intValue = Integer.parseInt(token);
  171. if (retValue == null) {
  172. retValue = new int[4];
  173. }
  174. else if(numCoords == retValue.length) {
  175. int[] temp = new int[retValue.length * 2];
  176. System.arraycopy(retValue, 0, temp, 0, retValue.length);
  177. retValue = temp;
  178. }
  179. retValue[numCoords++] = intValue * scale;
  180. } catch (NumberFormatException nfe) {
  181. return null;
  182. }
  183. }
  184. if (numCoords > 0 && numCoords != retValue.length) {
  185. int[] temp = new int[numCoords];
  186. System.arraycopy(retValue, 0, temp, 0, numCoords);
  187. retValue = temp;
  188. }
  189. return retValue;
  190. }
  191. /**
  192. * Defines the interface used for to check if a point is inside a
  193. * region.
  194. */
  195. interface RegionContainment {
  196. /**
  197. * Returns true if the location <code>x</code>, <code>y</code>
  198. * falls inside the region defined in the receiver.
  199. * <code>width</code>, <code>height</code> is the size of
  200. * the enclosing region.
  201. */
  202. public boolean contains(int x, int y, int width, int height);
  203. }
  204. /**
  205. * Used to test for containment in a rectangular region.
  206. */
  207. static class RectangleRegionContainment implements RegionContainment {
  208. /** Will be non-null if one of the values is a percent, and any value
  209. * that is non null indicates it is a percent
  210. * (order is x, y, width, height). */
  211. float[] percents;
  212. /** Last value of width passed in. */
  213. int lastWidth;
  214. /** Last value of height passed in. */
  215. int lastHeight;
  216. /** Top left. */
  217. int x0;
  218. int y0;
  219. /** Bottom right. */
  220. int x1;
  221. int y1;
  222. public RectangleRegionContainment(AttributeSet as) {
  223. int[] coords = Map.extractCoords(as.getAttribute(HTML.
  224. Attribute.COORDS));
  225. percents = null;
  226. if (coords == null || coords.length != 4) {
  227. throw new RuntimeException("Unable to parse rectangular area");
  228. }
  229. else {
  230. x0 = coords[0];
  231. y0 = coords[1];
  232. x1 = coords[2];
  233. y1 = coords[3];
  234. if (x0 < 0 || y0 < 0 || x1 < 0 || y1 < 0) {
  235. percents = new float[4];
  236. lastWidth = lastHeight = -1;
  237. for (int counter = 0; counter < 4; counter++) {
  238. if (coords[counter] < 0) {
  239. percents[counter] = Math.abs
  240. (coords[counter]) / 100.0f;
  241. }
  242. else {
  243. percents[counter] = -1.0f;
  244. }
  245. }
  246. }
  247. }
  248. }
  249. public boolean contains(int x, int y, int width, int height) {
  250. if (percents == null) {
  251. return contains(x, y);
  252. }
  253. if (lastWidth != width || lastHeight != height) {
  254. lastWidth = width;
  255. lastHeight = height;
  256. if (percents[0] != -1.0f) {
  257. x0 = (int)(percents[0] * width);
  258. }
  259. if (percents[1] != -1.0f) {
  260. y0 = (int)(percents[1] * height);
  261. }
  262. if (percents[2] != -1.0f) {
  263. x1 = (int)(percents[2] * width);
  264. }
  265. if (percents[3] != -1.0f) {
  266. y1 = (int)(percents[3] * height);
  267. }
  268. }
  269. return contains(x, y);
  270. }
  271. public boolean contains(int x, int y) {
  272. return ((x >= x0 && x <= x1) &&
  273. (y >= y0 && y <= y1));
  274. }
  275. }
  276. /**
  277. * Used to test for containment in a polygon region.
  278. */
  279. static class PolygonRegionContainment extends Polygon implements
  280. RegionContainment {
  281. /** If any value is a percent there will be an entry here for the
  282. * percent value. Use percentIndex to find out the index for it. */
  283. float[] percentValues;
  284. int[] percentIndexs;
  285. /** Last value of width passed in. */
  286. int lastWidth;
  287. /** Last value of height passed in. */
  288. int lastHeight;
  289. public PolygonRegionContainment(AttributeSet as) {
  290. int[] coords = Map.extractCoords(as.getAttribute(HTML.Attribute.
  291. COORDS));
  292. if (coords == null || coords.length == 0 ||
  293. coords.length % 2 != 0) {
  294. throw new RuntimeException("Unable to parse polygon area");
  295. }
  296. else {
  297. int numPercents = 0;
  298. lastWidth = lastHeight = -1;
  299. for (int counter = coords.length - 1; counter >= 0;
  300. counter--) {
  301. if (coords[counter] < 0) {
  302. numPercents++;
  303. }
  304. }
  305. if (numPercents > 0) {
  306. percentIndexs = new int[numPercents];
  307. percentValues = new float[numPercents];
  308. for (int counter = coords.length - 1, pCounter = 0;
  309. counter >= 0; counter--) {
  310. if (coords[counter] < 0) {
  311. percentValues[pCounter] = coords[counter] /
  312. -100.0f;
  313. percentIndexs[pCounter] = counter;
  314. pCounter++;
  315. }
  316. }
  317. }
  318. else {
  319. percentIndexs = null;
  320. percentValues = null;
  321. }
  322. npoints = coords.length / 2;
  323. xpoints = new int[npoints];
  324. ypoints = new int[npoints];
  325. for (int counter = 0; counter < npoints; counter++) {
  326. xpoints[counter] = coords[counter + counter];
  327. ypoints[counter] = coords[counter + counter + 1];
  328. }
  329. }
  330. }
  331. public boolean contains(int x, int y, int width, int height) {
  332. if (percentValues == null || (lastWidth == width &&
  333. lastHeight == height)) {
  334. return contains(x, y);
  335. }
  336. // Force the bounding box to be recalced.
  337. bounds = null;
  338. lastWidth = width;
  339. lastHeight = height;
  340. float fWidth = (float)width;
  341. float fHeight = (float)height;
  342. for (int counter = percentValues.length - 1; counter >= 0;
  343. counter--) {
  344. if (percentIndexs[counter] % 2 == 0) {
  345. // x
  346. xpoints[percentIndexs[counter] / 2] =
  347. (int)(percentValues[counter] * fWidth);
  348. }
  349. else {
  350. // y
  351. ypoints[percentIndexs[counter] / 2] =
  352. (int)(percentValues[counter] * fHeight);
  353. }
  354. }
  355. return contains(x, y);
  356. }
  357. }
  358. /**
  359. * Used to test for containment in a circular region.
  360. */
  361. static class CircleRegionContainment implements RegionContainment {
  362. /** X origin of the circle. */
  363. int x;
  364. /** Y origin of the circle. */
  365. int y;
  366. /** Radius of the circle. */
  367. int radiusSquared;
  368. /** Non-null indicates one of the values represents a percent. */
  369. float[] percentValues;
  370. /** Last value of width passed in. */
  371. int lastWidth;
  372. /** Last value of height passed in. */
  373. int lastHeight;
  374. public CircleRegionContainment(AttributeSet as) {
  375. int[] coords = Map.extractCoords(as.getAttribute(HTML.Attribute.
  376. COORDS));
  377. if (coords == null || coords.length != 3) {
  378. throw new RuntimeException("Unable to parse circular area");
  379. }
  380. x = coords[0];
  381. y = coords[1];
  382. radiusSquared = coords[2] * coords[2];
  383. if (coords[0] < 0 || coords[1] < 0 || coords[2] < 0) {
  384. lastWidth = lastHeight = -1;
  385. percentValues = new float[3];
  386. for (int counter = 0; counter < 3; counter++) {
  387. if (coords[counter] < 0) {
  388. percentValues[counter] = coords[counter] /
  389. -100.0f;
  390. }
  391. else {
  392. percentValues[counter] = -1.0f;
  393. }
  394. }
  395. }
  396. else {
  397. percentValues = null;
  398. }
  399. }
  400. public boolean contains(int x, int y, int width, int height) {
  401. if (percentValues != null && (lastWidth != width ||
  402. lastHeight != height)) {
  403. int newRad = Math.min(width, height) / 2;
  404. lastWidth = width;
  405. lastHeight = height;
  406. if (percentValues[0] != -1.0f) {
  407. this.x = (int)(percentValues[0] * width);
  408. }
  409. if (percentValues[1] != -1.0f) {
  410. this.y = (int)(percentValues[1] * height);
  411. }
  412. if (percentValues[2] != -1.0f) {
  413. radiusSquared = (int)(percentValues[2] *
  414. Math.min(width, height));
  415. radiusSquared *= radiusSquared;
  416. }
  417. }
  418. return (((x - this.x) * (x - this.x) +
  419. (y - this.y) * (y - this.y)) <= radiusSquared);
  420. }
  421. }
  422. /**
  423. * An implementation that will return true if the x, y location is
  424. * inside a rectangle defined by origin 0, 0, and width equal to
  425. * width passed in, and height equal to height passed in.
  426. */
  427. static class DefaultRegionContainment implements RegionContainment {
  428. /** A global shared instance. */
  429. static DefaultRegionContainment si = null;
  430. public static DefaultRegionContainment sharedInstance() {
  431. if (si == null) {
  432. si = new DefaultRegionContainment();
  433. }
  434. return si;
  435. }
  436. public boolean contains(int x, int y, int width, int height) {
  437. return (x <= width && x >= 0 && y >= 0 && y <= width);
  438. }
  439. }
  440. }