2 **********************************************************************
\r
3 * Copyright (c) 2009-2010, Google, International Business Machines
\r
4 * Corporation and others. All Rights Reserved.
\r
5 **********************************************************************
\r
7 **********************************************************************
\r
9 package com.ibm.icu.dev.tool.cldr;
\r
10 import java.awt.Font;
\r
11 import java.awt.GraphicsEnvironment;
\r
12 import java.awt.Shape;
\r
13 import java.awt.font.FontRenderContext;
\r
14 import java.awt.font.GlyphVector;
\r
15 import java.awt.geom.AffineTransform;
\r
16 import java.awt.geom.PathIterator;
\r
17 import java.awt.geom.Rectangle2D;
\r
18 import java.io.BufferedReader;
\r
19 import java.io.File;
\r
20 import java.io.IOException;
\r
21 import java.io.PrintWriter;
\r
22 import java.util.Arrays;
\r
23 import java.util.Collection;
\r
24 import java.util.Comparator;
\r
25 import java.util.HashMap;
\r
26 import java.util.HashSet;
\r
27 import java.util.Map;
\r
28 import java.util.Set;
\r
29 import java.util.TreeMap;
\r
30 import java.util.TreeSet;
\r
31 import java.util.regex.Matcher;
\r
32 import java.util.regex.Pattern;
\r
34 import com.ibm.icu.dev.test.util.BagFormatter;
\r
35 import com.ibm.icu.dev.test.util.TransliteratorUtilities;
\r
36 import com.ibm.icu.dev.test.util.UnicodeMap;
\r
37 import com.ibm.icu.dev.test.util.UnicodeMapIterator;
\r
38 import com.ibm.icu.dev.test.util.Tabber.HTMLTabber;
\r
39 import com.ibm.icu.dev.test.util.UnicodeMap.Composer;
\r
40 import com.ibm.icu.dev.test.util.XEquivalenceClass.SetMaker;
\r
41 import com.ibm.icu.impl.Row;
\r
42 import com.ibm.icu.impl.Utility;
\r
43 import com.ibm.icu.impl.Row.R2;
\r
44 import com.ibm.icu.lang.UCharacter;
\r
45 import com.ibm.icu.lang.UScript;
\r
46 import com.ibm.icu.text.Collator;
\r
47 import com.ibm.icu.text.Normalizer;
\r
48 import com.ibm.icu.text.UTF16;
\r
49 import com.ibm.icu.text.UnicodeSet;
\r
50 import com.ibm.icu.text.UnicodeSetIterator;
\r
53 public class CheckSystemFonts {
\r
55 static String outputDirectoryName;
\r
56 static Set<String> SKIP_SHAPES = new HashSet<String>();
\r
58 public static void main(String[] args) throws IOException {
\r
59 System.out.println("Arguments:\t" + Arrays.asList(args));
\r
60 if (args.length < 2) {
\r
61 throw new IllegalArgumentException("Need command-line args:" +
\r
62 "\n\t\tfont-name-regex" +
\r
63 "\n\t\toutput-directory"
\r
66 Matcher nameMatcher = Pattern.compile(args[0], Pattern.CASE_INSENSITIVE).matcher("");
\r
67 outputDirectoryName = args[1].trim();
\r
68 File outputDirectory = new File(outputDirectoryName);
\r
69 if (!outputDirectory.isDirectory()) {
\r
70 throw new IllegalArgumentException("2nd arg must be valid directory");
\r
74 Map<UnicodeSet,Set<String>> data = new TreeMap<UnicodeSet, Set<String>>();
\r
75 Map<String, Font> fontMap = new TreeMap<String, Font>();
\r
76 getFontData(nameMatcher, data, fontMap);
\r
81 UnicodeMap<Set<String>> map = showEquivalentCoverage(data);
\r
83 showRawCoverage(data);
\r
85 Map<Set<String>, String> toShortName = showRawCoverage(map);
\r
87 showFullCoverage(map, toShortName);
\r
90 private static void loadSkipShapes() {
\r
92 BufferedReader in = BagFormatter.openUTF8Reader(outputDirectoryName, "skip_fonts.txt");
\r
94 String line = in.readLine();
\r
95 if (line == null) break;
\r
96 String[] fonts = line.trim().split("\\s+");
\r
97 for (String font : fonts) {
\r
98 SKIP_SHAPES.add(font);
\r
102 } catch (IOException e) {
\r
103 System.err.println("Couldn't open:\t" + outputDirectoryName + "/" + "skip_fonts.txt");
\r
108 private static final Collator English = Collator.getInstance();
\r
111 English.setStrength(Collator.SECONDARY);
\r
114 public static final UnicodeSet DONT_CARE = new UnicodeSet("[[:cn:][:co:][:cs:]]").freeze();
\r
115 public static final UnicodeSet COVERAGE = new UnicodeSet(DONT_CARE).complement().freeze();
\r
117 private static final Comparator<String> SHORTER_FIRST = new Comparator<String>() {
\r
118 public int compare(String n1, String n2) {
\r
119 int result = n1.length() - n2.length();
\r
120 if (result != 0) return result;
\r
121 return n1.compareTo(n2);
\r
125 private static final Comparator<UnicodeSet> LONGER_SET_FIRST = new Comparator<UnicodeSet>() {
\r
126 public int compare(UnicodeSet n1, UnicodeSet n2) {
\r
127 int result = n1.size() - n2.size();
\r
128 if (result != 0) return -result;
\r
129 return n1.compareTo(n2);
\r
133 private static final Comparator<Collection> SHORTER_COLLECTION_FIRST = new Comparator<Collection>() {
\r
134 public int compare(Collection n1, Collection n2) {
\r
135 int result = n1.size() - n2.size();
\r
136 if (result != 0) return result;
\r
137 return UnicodeSet.compare(n1, n2);
\r
141 private static final HashSet SKIP_TERMS = new HashSet(Arrays.asList("black", "blackitalic", "bold", "boldit", "bolditalic", "bolditalicmt", "boldmt",
\r
142 "boldob", "boldoblique", "boldslanted", "book", "bookitalic", "condensed", "condensedblack", "condensedbold", "condensedextrabold",
\r
143 "condensedlight", "condensedmedium", "extracondensed", "extralight", "heavy", "italic", "italicmt", "light", "lightit", "lightitalic", "medium",
\r
144 "mediumitalic", "oblique", "regular", "roman", "semibold", "semibolditalic", "shadow", "slanted", "ultrabold", "ultralight", "ultralightitalic"
\r
147 private static Composer<Set<String>> composer = new Composer<Set<String>>() {
\r
148 Map<R2<Set<String>, Set<String>>,Set<String>> cache = new HashMap<R2<Set<String>, Set<String>>,Set<String>>();
\r
149 public Set<String> compose(int codePoint, String string, Set<String> a, Set<String> b) {
\r
150 return a == null ? b
\r
151 : b == null ? null
\r
154 private Set<String> intern(Set<String> a, Set<String> b) {
\r
155 R2<Set<String>, Set<String>> row = Row.of(a, b);
\r
156 Set<String> result = cache.get(row);
\r
157 if (result == null) {
\r
158 result = new TreeSet<String>(English);
\r
161 cache.put(row, result);
\r
168 private static void showFullCoverage(UnicodeMap<Set<String>> map, Map<Set<String>, String> toShortName) throws IOException {
\r
169 System.out.println("\n***COVERAGE:\t" + map.keySet().size() + "\n");
\r
170 PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "coverage.txt");
\r
172 for (UnicodeMapIterator<String> it = new UnicodeMapIterator<String>(map); it.nextRange();) {
\r
173 String codes = "U+" + Utility.hex(it.codepoint);
\r
174 String names = UCharacter.getExtendedName(it.codepoint);
\r
175 if (it.codepointEnd != it.codepoint) {
\r
176 codes += "..U+" + Utility.hex(it.codepointEnd);
\r
177 names += ".." + UCharacter.getExtendedName(it.codepointEnd);
\r
179 out.println(codes + "\t" + toShortName.get(map.get(it.codepoint)) + "\t" + names);
\r
182 UnicodeSet missing = new UnicodeSet(COVERAGE).removeAll(map.keySet());
\r
183 out.println("\nMISSING:\t" + missing.size() + "\n");
\r
185 UnicodeMap<String> missingMap = new UnicodeMap<String>();
\r
186 for (UnicodeSetIterator it = new UnicodeSetIterator(missing); it.next();) {
\r
187 missingMap.put(it.codepoint, UScript.getName(UScript.getScript(it.codepoint)) + "-" + getShortAge(it.codepoint));
\r
190 Set<String> sorted = new TreeSet<String>(English);
\r
191 sorted.addAll(missingMap.values());
\r
192 for (String value : sorted) {
\r
193 UnicodeSet items = missingMap.getSet(value);
\r
194 for (UnicodeSetIterator it = new UnicodeSetIterator(items); it.nextRange();) {
\r
195 String codes = "U+" + Utility.hex(it.codepoint);
\r
196 String names = UCharacter.getExtendedName(it.codepoint);
\r
197 if (it.codepointEnd != it.codepoint) {
\r
198 codes += "..U+" + Utility.hex(it.codepointEnd);
\r
199 names += ".." + UCharacter.getExtendedName(it.codepointEnd);
\r
201 out.println(codes + "\t" + value + "\t" + names);
\r
208 private static Map<Set<String>, String> showRawCoverage(UnicodeMap<Set<String>> map) throws IOException {
\r
209 System.out.println("\n***COMBO NAMES\n");
\r
210 PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "combo_names.txt");
\r
213 Map<Set<String>, String> toShortName = new HashMap<Set<String>, String>();
\r
214 TreeSet<Set<String>> sortedValues = new TreeSet<Set<String>>(SHORTER_COLLECTION_FIRST);
\r
215 sortedValues.addAll(map.values());
\r
216 for (Set<String> value : sortedValues) {
\r
217 String shortName = "combo" + count++;
\r
218 Set<String> contained = getLargestContained(value, toShortName.keySet());
\r
220 if (contained != null) {
\r
221 Set<String> remainder = new TreeSet<String>();
\r
222 remainder.addAll(value);
\r
223 remainder.removeAll(contained);
\r
224 valueName = toShortName.get(contained) + " + " + remainder;
\r
226 valueName = value.toString();
\r
228 toShortName.put(value, shortName);
\r
229 out.println(shortName + "\t" + valueName);
\r
232 return toShortName;
\r
235 private static void showRawCoverage(Map<UnicodeSet, Set<String>> data) throws IOException {
\r
236 System.out.println("\n***RAW COVERAGE (bridging unassigned)\n");
\r
237 PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "raw_coverage.txt");
\r
239 for (UnicodeSet s : data.keySet()) {
\r
240 Set<String> nameSet = data.get(s);
\r
241 String name = nameSet.iterator().next();
\r
242 UnicodeSet bridged = new UnicodeSet(s).addBridges(DONT_CARE);
\r
243 out.println(name + "\t" + s.size() + "\t" + bridged);
\r
248 private static UnicodeMap<Set<String>> showEquivalentCoverage(Map<UnicodeSet, Set<String>> data) throws IOException {
\r
249 System.out.println("\n***EQUIVALENT COVERAGE\n");
\r
250 PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "equiv_coverage.txt");
\r
252 UnicodeMap<Set<String>> map = new UnicodeMap<Set<String>>();
\r
254 Map<String,Set<String>> nameToSingleton = new HashMap<String,Set<String>>();
\r
256 for (UnicodeSet s : data.keySet()) {
\r
257 Set<String> nameSet = data.get(s);
\r
258 String name = nameSet.iterator().next();
\r
259 //System.out.println(s);
\r
260 Set<String> temp2 = nameToSingleton.get(name);
\r
261 if (temp2 == null) {
\r
262 temp2 = new TreeSet<String>(English);
\r
265 map.composeWith(s, temp2, composer);
\r
266 if (nameSet.size() > 1) {
\r
267 TreeSet<String> temp = new TreeSet<String>(English);
\r
268 temp.addAll(nameSet);
\r
270 out.println(name + "\t" + temp);
\r
277 private static void showSameGlyphs() throws IOException {
\r
278 System.out.println("\n***Visual Equivalences");
\r
279 PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "same_glyphs.txt");
\r
280 PrintWriter out2 = BagFormatter.openUTF8Writer(outputDirectoryName, "same_glyphs.html");
\r
281 out2.println("<html><head>");
\r
282 out2.println("<meta content=\"text/html; charset=utf-8\" http-equiv=Content-Type></HEAD>");
\r
283 out2.println("<link rel='stylesheet' href='index.css' type='text/css'>");
\r
284 out2.println("</head><body><table>");
\r
285 HTMLTabber tabber = new HTMLTabber();
\r
287 out2.println(tabber.process("Code1\tCode2\tNFC1\tNFC1\tCh1\tCh1\tCh1/F\tCh2/F\tName1\tName2\tFonts"));
\r
288 tabber.setParameters(0, "class='c'");
\r
289 tabber.setParameters(1, "class='c'");
\r
290 tabber.setParameters(2, "class='nf'");
\r
291 tabber.setParameters(3, "class='nf'");
\r
292 tabber.setParameters(4, "class='p'");
\r
293 tabber.setParameters(5, "class='p'");
\r
294 //tabber.setParameters(6, "class='q'");
\r
295 //tabber.setParameters(7, "class='q'");
\r
296 tabber.setParameters(8, "class='n'");
\r
297 tabber.setParameters(9, "class='n'");
\r
298 tabber.setParameters(10, "class='f'");
\r
300 for (R2<Integer,Integer> sample : equivalences.keySet()) {
\r
301 final Set<String> reasonSet = equivalences.get(sample);
\r
302 String reasons = reasonSet.toString();
\r
303 if (reasons.length() > 100) reasons = reasons.substring(0,100) + "...";
\r
304 final Integer codepoint1 = sample.get0();
\r
305 final Integer codepoint2 = sample.get1();
\r
307 out.println("U+" + Utility.hex(codepoint1) + "\t" + "U+" + Utility.hex(codepoint2)
\r
308 + "\t" + showNfc(codepoint1) + "\t" + showNfc(codepoint2)
\r
309 + "\t" + showChar(codepoint1, false) + "\t" + showChar(codepoint2, false)
\r
310 + "\t" + UCharacter.getExtendedName(codepoint1) + "\t" + UCharacter.getExtendedName(codepoint2)
\r
312 String line = "U+" + Utility.hex(codepoint1) + "\t" + "U+" + Utility.hex(codepoint2)
\r
313 + "\t" + showNfc(codepoint1) + "\t" + showNfc(codepoint2)
\r
314 + "\t" + showChar(codepoint1, false) + "\t" + showChar(codepoint2, true)
\r
315 + "\t" + showChar(codepoint1, false) + "\t" + showChar(codepoint2, true)
\r
316 + "\t" + UCharacter.getExtendedName(codepoint1) + "\t" + UCharacter.getExtendedName(codepoint2)
\r
319 String fonts = "class='q' style='font-family:";
\r
321 for (String font : reasonSet) {
\r
322 if (maxCount != 5) {
\r
327 if (maxCount <= 0) break;
\r
330 tabber.setParameters(6, fonts);
\r
331 tabber.setParameters(7, fonts);
\r
332 out2.println(tabber.process(line));
\r
334 out2.println("</table></body>");
\r
339 private static void showInvisibles() throws IOException {
\r
340 System.out.println("\n***Invisibles Equivalences");
\r
341 PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "invisibles.txt");
\r
342 for (String sample : invisibles) {
\r
343 String reasons = invisibles.get(sample).toString();
\r
344 if (reasons.length() > 100) reasons = reasons.substring(0,100) + "...";
\r
345 int codepoint = sample.codePointAt(0);
\r
346 out.println("U+" + Utility.hex(sample)
\r
347 + "\t" + showChar(codepoint, false)
\r
348 + "\t" + showNfc(codepoint)
\r
349 + "\t" + UCharacter.getExtendedName(codepoint)
\r
356 private static void getFontData(Matcher nameMatcher, Map<UnicodeSet, Set<String>> data, Map<String, Font> fontMap) {
\r
357 GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
\r
358 Font[] fonts = env.getAllFonts();
\r
359 for (Font font : fonts) {
\r
360 if (!font.isPlain()) {
\r
363 String name = font.getName();
\r
364 int lastDash = name.lastIndexOf('-');
\r
365 String term = lastDash < 0 ? "" : name.substring(lastDash+1).toLowerCase();
\r
366 if (SKIP_TERMS.contains(term)) {
\r
369 if (nameMatcher != null && !nameMatcher.reset(name).find()) {
\r
372 fontMap.put(name,font);
\r
374 for (String name : fontMap.keySet()) {
\r
375 Font font = fontMap.get(name);
\r
376 System.out.println(name);
\r
377 UnicodeSet coverage = getCoverage(font);
\r
378 Set<String> sameFonts = data.get(coverage);
\r
379 if (sameFonts == null) {
\r
380 data.put(coverage, sameFonts = new TreeSet<String>(SHORTER_FIRST));
\r
382 System.out.println("\tNote: same coverage as " + sameFonts.iterator().next());
\r
384 sameFonts.add(name);
\r
388 static Comparator<Integer> NFCLower = new Comparator<Integer>() {
\r
389 public int compare(Integer o1, Integer o2) {
\r
390 boolean n1 = Normalizer.isNormalized(o1, Normalizer.NFC, 0);
\r
391 boolean n2 = Normalizer.isNormalized(o2, Normalizer.NFC, 0);
\r
392 if (n1 != n2) return n1 ? -1 : 1;
\r
393 n1 = Normalizer.isNormalized(o1, Normalizer.NFKC, 0);
\r
394 n2 = Normalizer.isNormalized(o2, Normalizer.NFKC, 0);
\r
395 if (n1 != n2) return n1 ? -1 : 1;
\r
396 return o1.compareTo(o2);
\r
400 static Comparator<R2<Integer,Integer>> NFCLowerR2 = new Comparator<R2<Integer,Integer>>() {
\r
401 public int compare(R2<Integer, Integer> o1, R2<Integer, Integer> o2) {
\r
402 int diff = NFCLower.compare(o1.get0(), o2.get0());
\r
403 if (diff != 0) return diff;
\r
404 return NFCLower.compare(o1.get1(), o2.get1());
\r
408 private static String showNfc(int codepoint) {
\r
409 return Normalizer.isNormalized(codepoint, Normalizer.NFC, 0) ? ""
\r
410 : Normalizer.isNormalized(codepoint, Normalizer.NFKC, 0) ? "!C" : "!K";
\r
413 private static String showChar(Integer item, boolean html) {
\r
414 return rtlProtect(UTF16.valueOf(item), html);
\r
416 static UnicodeSet RTL = new UnicodeSet("[[:bc=R:][:bc=AL:][:bc=AN:]]").freeze();
\r
417 static UnicodeSet CONTROLS = new UnicodeSet("[[:cc:][:Zl:][:Zp:]]").freeze();
\r
418 static UnicodeSet INVISIBLES = new UnicodeSet("[:di:]").freeze();
\r
419 static final char LRM = '\u200E';
\r
421 private static String rtlProtect(String source, boolean html) {
\r
422 if (CONTROLS.containsSome(source)) {
\r
424 } else if (INVISIBLES.containsSome(source)) {
\r
426 } else if (RTL.containsSome(source) || source.startsWith("\"")) {
\r
427 source = LRM + source + LRM;
\r
429 return html ? TransliteratorUtilities.toHTML.transform(source) : source;
\r
433 private static Set<String> getLargestContained(Set<String> value, Collection<Set<String>> collection) {
\r
434 Set<String> best = null;
\r
435 for (Set<String> set : collection) {
\r
436 if (best != null && best.size() > set.size()) {
\r
439 if (value.containsAll(set)) {
\r
446 private static String getShortAge(int i) {
\r
447 String age = UCharacter.getAge(i).toString();
\r
448 return age.substring(0,age.indexOf('.',age.indexOf('.') + 1));
\r
451 static SetMaker setMaker = new SetMaker() {
\r
452 public Set make() {
\r
453 return new TreeSet();
\r
457 static UnicodeMap<Set<String>> invisibles = new UnicodeMap();
\r
458 static Map<R2<Integer,Integer>, Set<String>> equivalences = new TreeMap<R2<Integer,Integer>, Set<String>>(NFCLowerR2);
\r
459 // static Set<String> SKIP_SHAPES = new HashSet<String>(Arrays.asList(
\r
462 // "DFKaiShu-SB-Estd-BF",
\r
468 // "SIL-Hei-Med-Jian",
\r
469 // "SIL-Kai-Reg-Jian",
\r
472 // "HelveticaCYBoldOblique",
\r
473 // "HelveticaCYOblique",
\r
474 // "HelveticaCYPlain",
\r
475 // "HoeflerText-Ornaments",
\r
476 // "Apple-Chancery",
\r
477 // "MSReferenceSpecialty",
\r
485 // "RosewoodStd-Fill",
\r
503 // bug on Mac: http://forums.sun.com/thread.jspa?threadID=5209611
\r
504 private static UnicodeSet getCoverage(Font font) {
\r
505 String name = font.getFontName();
\r
506 boolean skipShapes = SKIP_SHAPES.contains(name);
\r
507 UnicodeSet result = new UnicodeSet();
\r
508 final FontRenderContext fontRenderContext = new FontRenderContext(null, false, false);
\r
509 char[] array = new char[1];
\r
510 char[] array2 = new char[2];
\r
511 Map<Rectangle2D,Map<Shape,UnicodeSet>> boundsToData = new TreeMap<Rectangle2D,Map<Shape,UnicodeSet>>(ShapeComparator);
\r
512 for (UnicodeSetIterator it = new UnicodeSetIterator(COVERAGE); it.next();) {
\r
513 if (font.canDisplay(it.codepoint)) {
\r
515 if (it.codepoint <= 0xFFFF) {
\r
516 array[0] = (char) it.codepoint;
\r
519 Character.toChars(it.codepoint, array2, 0);
\r
523 GlyphVector glyphVector = font.createGlyphVector(fontRenderContext, temp);
\r
524 int glyphCode = glyphVector.getGlyphCode(0);
\r
525 boolean validchar = (glyphCode > 0);
\r
526 if (!validchar) continue;
\r
528 result.add(it.codepoint);
\r
530 if (skipShapes) continue;
\r
531 Shape shape = glyphVector.getOutline();
\r
532 if (isInvisible(shape)) {
\r
533 Set<String> set = invisibles.get(it.codepoint);
\r
535 invisibles.put(it.codepoint, set = new TreeSet<String>());
\r
539 Rectangle2D bounds = glyphVector.getVisualBounds();
\r
540 Map<Shape, UnicodeSet> map = boundsToData.get(bounds);
\r
542 boundsToData.put(bounds, map = new TreeMap<Shape,UnicodeSet>(ShapeComparator));
\r
544 UnicodeSet set = map.get(shape);
\r
546 map.put(shape, set = new UnicodeSet());
\r
548 if (false && set.size() != 0) {
\r
549 System.out.println("Adding " + Utility.hex(it.codepoint) + "\t" + UTF16.valueOf(it.codepoint) + "\tto " + set.toPattern(false));
\r
551 set.add(it.codepoint);
\r
555 //System.out.println(result.size() + "\t" + result);
\r
556 for (Rectangle2D bounds : boundsToData.keySet()) {
\r
557 Map<Shape, UnicodeSet> map = boundsToData.get(bounds);
\r
558 for (Shape shape : map.keySet()) {
\r
559 UnicodeSet set = map.get(shape);
\r
560 set.removeAll(CONTROLS);
\r
561 if (set.size() != 1) {
\r
562 //System.out.println(set.toPattern(false));
\r
563 for (UnicodeSetIterator it = new UnicodeSetIterator(set); it.next();) {
\r
564 for (UnicodeSetIterator it2 = new UnicodeSetIterator(set); it2.next();) {
\r
565 int cp = it.codepoint;
\r
566 int cp2 = it2.codepoint;
\r
567 if (cp >= cp2) continue;
\r
568 R2<Integer, Integer> r = Row.of(cp, cp2);
\r
569 Set<String> reasons = equivalences.get(r);
\r
570 if (reasons == null) {
\r
571 equivalences.put(r, reasons = new TreeSet());
\r
579 return result.freeze();
\r
582 static Comparator<Rectangle2D> RectComparator = new Comparator<Rectangle2D>() {
\r
584 public int compare(Rectangle2D r1, Rectangle2D r2) {
\r
586 if (0 != (diff = compareDiff(r1.getX(),r2.getX()))) return diff;
\r
587 if (0 != (diff = compareDiff(r1.getY(),r2.getY()))) return diff;
\r
588 if (0 != (diff = compareDiff(r1.getWidth(),r2.getWidth()))) return diff;
\r
589 if (0 != (diff = compareDiff(r1.getHeight(),r2.getHeight()))) return diff;
\r
595 static final AffineTransform IDENTITY = new AffineTransform();
\r
597 static boolean isInvisible(Shape shape) {
\r
598 return shape.getPathIterator(IDENTITY).isDone();
\r
601 static Comparator<Shape> ShapeComparator = new Comparator<Shape>() {
\r
602 float[] coords1 = new float[6];
\r
603 float[] coords2 = new float[6];
\r
605 public int compare(Shape s1, Shape s2) {
\r
607 PathIterator p1 = s1.getPathIterator(IDENTITY);
\r
608 PathIterator p2 = s2.getPathIterator(IDENTITY);
\r
611 return p2.isDone() ? 0 : -1;
\r
612 } else if (p2.isDone()) {
\r
615 int t1 = p1.currentSegment(coords1);
\r
616 int t2 = p2.currentSegment(coords2);
\r
618 if (diff != 0) return diff;
\r
620 * SEG_MOVETO and SEG_LINETO types returns one point,
\r
621 * SEG_QUADTO returns two points,
\r
622 * SEG_CUBICTO returns 3 points
\r
623 * and SEG_CLOSE does not return any points.
\r
626 case PathIterator.SEG_CUBICTO:
\r
627 if (0 != (diff = compareDiff(coords1[5],coords2[5]))) return diff;
\r
628 if (0 != (diff = compareDiff(coords1[4],coords2[4]))) return diff;
\r
629 case PathIterator.SEG_QUADTO:
\r
630 if (0 != (diff = compareDiff(coords1[3],coords2[3]))) return diff;
\r
631 if (0 != (diff = compareDiff(coords1[2],coords2[2]))) return diff;
\r
632 case PathIterator.SEG_MOVETO:
\r
633 case PathIterator.SEG_LINETO:
\r
634 if (0 != (diff = compareDiff(coords1[1],coords2[1]))) return diff;
\r
635 if (0 != (diff = compareDiff(coords1[0],coords2[0]))) return diff;
\r
636 case PathIterator.SEG_CLOSE: break;
\r
637 default: throw new IllegalArgumentException();
\r
645 private static int compareDiff(float f, float g) {
\r
646 return f < g ? -1 : f > g ? 1 : 0;
\r
648 private static int compareDiff(double f, double g) {
\r
649 return f < g ? -1 : f > g ? 1 : 0;
\r