| /* |
| ********************************************************************** |
| * Copyright (c) 2009-2010, Google, International Business Machines |
| * Corporation and others. All Rights Reserved. |
| ********************************************************************** |
| * Author: Mark Davis |
| ********************************************************************** |
| */ |
| package com.ibm.icu.dev.tool.cldr; |
| import java.awt.Font; |
| import java.awt.GraphicsEnvironment; |
| import java.awt.Shape; |
| import java.awt.font.FontRenderContext; |
| import java.awt.font.GlyphVector; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.PathIterator; |
| import java.awt.geom.Rectangle2D; |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import com.ibm.icu.dev.test.util.BagFormatter; |
| import com.ibm.icu.dev.test.util.Tabber.HTMLTabber; |
| import com.ibm.icu.dev.test.util.TransliteratorUtilities; |
| import com.ibm.icu.dev.test.util.UnicodeMap; |
| import com.ibm.icu.dev.test.util.UnicodeMap.Composer; |
| import com.ibm.icu.dev.test.util.UnicodeMapIterator; |
| import com.ibm.icu.dev.test.util.XEquivalenceClass.SetMaker; |
| import com.ibm.icu.impl.Row; |
| import com.ibm.icu.impl.Row.R2; |
| import com.ibm.icu.impl.Utility; |
| import com.ibm.icu.lang.UCharacter; |
| import com.ibm.icu.lang.UScript; |
| import com.ibm.icu.text.Collator; |
| import com.ibm.icu.text.Normalizer; |
| import com.ibm.icu.text.UTF16; |
| import com.ibm.icu.text.UnicodeSet; |
| import com.ibm.icu.text.UnicodeSetIterator; |
| |
| |
| public class CheckSystemFonts { |
| |
| static String outputDirectoryName; |
| static Set<String> SKIP_SHAPES = new HashSet<String>(); |
| |
| public static void main(String[] args) throws IOException { |
| System.out.println("Arguments:\t" + Arrays.asList(args)); |
| if (args.length < 2) { |
| throw new IllegalArgumentException("Need command-line args:" + |
| "\n\t\tfont-name-regex" + |
| "\n\t\toutput-directory" |
| ); |
| } |
| Matcher nameMatcher = Pattern.compile(args[0], Pattern.CASE_INSENSITIVE).matcher(""); |
| outputDirectoryName = args[1].trim(); |
| File outputDirectory = new File(outputDirectoryName); |
| if (!outputDirectory.isDirectory()) { |
| throw new IllegalArgumentException("2nd arg must be valid directory"); |
| } |
| loadSkipShapes(); |
| |
| Map<UnicodeSet,Set<String>> data = new TreeMap<UnicodeSet, Set<String>>(); |
| Map<String, Font> fontMap = new TreeMap<String, Font>(); |
| getFontData(nameMatcher, data, fontMap); |
| |
| showInvisibles(); |
| showSameGlyphs(); |
| |
| UnicodeMap<Set<String>> map = showEquivalentCoverage(data); |
| |
| showRawCoverage(data); |
| |
| Map<Set<String>, String> toShortName = showRawCoverage(map); |
| |
| showFullCoverage(map, toShortName); |
| } |
| |
| private static void loadSkipShapes() { |
| try { |
| BufferedReader in = BagFormatter.openUTF8Reader(outputDirectoryName, "skip_fonts.txt"); |
| while (true) { |
| String line = in.readLine(); |
| if (line == null) break; |
| String[] fonts = line.trim().split("\\s+"); |
| for (String font : fonts) { |
| SKIP_SHAPES.add(font); |
| } |
| } |
| in.close(); |
| } catch (IOException e) { |
| System.err.println("Couldn't open:\t" + outputDirectoryName + "/" + "skip_fonts.txt"); |
| } |
| } |
| |
| |
| private static final Collator English = Collator.getInstance(); |
| |
| static { |
| English.setStrength(Collator.SECONDARY); |
| } |
| |
| public static final UnicodeSet DONT_CARE = new UnicodeSet("[[:cn:][:co:][:cs:]]").freeze(); |
| public static final UnicodeSet COVERAGE = new UnicodeSet(DONT_CARE).complement().freeze(); |
| |
| private static final Comparator<String> SHORTER_FIRST = new Comparator<String>() { |
| public int compare(String n1, String n2) { |
| int result = n1.length() - n2.length(); |
| if (result != 0) return result; |
| return n1.compareTo(n2); |
| } |
| }; |
| |
| private static final Comparator<UnicodeSet> LONGER_SET_FIRST = new Comparator<UnicodeSet>() { |
| public int compare(UnicodeSet n1, UnicodeSet n2) { |
| int result = n1.size() - n2.size(); |
| if (result != 0) return -result; |
| return n1.compareTo(n2); |
| } |
| }; |
| |
| private static final Comparator<Collection> SHORTER_COLLECTION_FIRST = new Comparator<Collection>() { |
| public int compare(Collection n1, Collection n2) { |
| int result = n1.size() - n2.size(); |
| if (result != 0) return result; |
| return UnicodeSet.compare(n1, n2); |
| } |
| }; |
| |
| private static final HashSet SKIP_TERMS = new HashSet(Arrays.asList("black", "blackitalic", "bold", "boldit", "bolditalic", "bolditalicmt", "boldmt", |
| "boldob", "boldoblique", "boldslanted", "book", "bookitalic", "condensed", "condensedblack", "condensedbold", "condensedextrabold", |
| "condensedlight", "condensedmedium", "extracondensed", "extralight", "heavy", "italic", "italicmt", "light", "lightit", "lightitalic", "medium", |
| "mediumitalic", "oblique", "regular", "roman", "semibold", "semibolditalic", "shadow", "slanted", "ultrabold", "ultralight", "ultralightitalic" |
| )); |
| |
| private static Composer<Set<String>> composer = new Composer<Set<String>>() { |
| Map<R2<Set<String>, Set<String>>,Set<String>> cache = new HashMap<R2<Set<String>, Set<String>>,Set<String>>(); |
| public Set<String> compose(int codePoint, String string, Set<String> a, Set<String> b) { |
| return a == null ? b |
| : b == null ? null |
| : intern(a,b); |
| } |
| private Set<String> intern(Set<String> a, Set<String> b) { |
| R2<Set<String>, Set<String>> row = Row.of(a, b); |
| Set<String> result = cache.get(row); |
| if (result == null) { |
| result = new TreeSet<String>(English); |
| result.addAll(a); |
| result.addAll(b); |
| cache.put(row, result); |
| } |
| return result; |
| } |
| }; |
| |
| |
| private static void showFullCoverage(UnicodeMap<Set<String>> map, Map<Set<String>, String> toShortName) throws IOException { |
| System.out.println("\n***COVERAGE:\t" + map.keySet().size() + "\n"); |
| PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "coverage.txt"); |
| |
| for (UnicodeMapIterator<String> it = new UnicodeMapIterator<String>(map); it.nextRange();) { |
| String codes = "U+" + Utility.hex(it.codepoint); |
| String names = UCharacter.getExtendedName(it.codepoint); |
| if (it.codepointEnd != it.codepoint) { |
| codes += "..U+" + Utility.hex(it.codepointEnd); |
| names += ".." + UCharacter.getExtendedName(it.codepointEnd); |
| } |
| out.println(codes + "\t" + toShortName.get(map.get(it.codepoint)) + "\t" + names); |
| } |
| |
| UnicodeSet missing = new UnicodeSet(COVERAGE).removeAll(map.keySet()); |
| out.println("\nMISSING:\t" + missing.size() + "\n"); |
| |
| UnicodeMap<String> missingMap = new UnicodeMap<String>(); |
| for (UnicodeSetIterator it = new UnicodeSetIterator(missing); it.next();) { |
| missingMap.put(it.codepoint, UScript.getName(UScript.getScript(it.codepoint)) + "-" + getShortAge(it.codepoint)); |
| } |
| |
| Set<String> sorted = new TreeSet<String>(English); |
| sorted.addAll(missingMap.values()); |
| for (String value : sorted) { |
| UnicodeSet items = missingMap.getSet(value); |
| for (UnicodeSetIterator it = new UnicodeSetIterator(items); it.nextRange();) { |
| String codes = "U+" + Utility.hex(it.codepoint); |
| String names = UCharacter.getExtendedName(it.codepoint); |
| if (it.codepointEnd != it.codepoint) { |
| codes += "..U+" + Utility.hex(it.codepointEnd); |
| names += ".." + UCharacter.getExtendedName(it.codepointEnd); |
| } |
| out.println(codes + "\t" + value + "\t" + names); |
| } |
| out.println(); |
| } |
| out.close(); |
| } |
| |
| private static Map<Set<String>, String> showRawCoverage(UnicodeMap<Set<String>> map) throws IOException { |
| System.out.println("\n***COMBO NAMES\n"); |
| PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "combo_names.txt"); |
| |
| int count = 0; |
| Map<Set<String>, String> toShortName = new HashMap<Set<String>, String>(); |
| TreeSet<Set<String>> sortedValues = new TreeSet<Set<String>>(SHORTER_COLLECTION_FIRST); |
| sortedValues.addAll(map.values()); |
| for (Set<String> value : sortedValues) { |
| String shortName = "combo" + count++; |
| Set<String> contained = getLargestContained(value, toShortName.keySet()); |
| String valueName; |
| if (contained != null) { |
| Set<String> remainder = new TreeSet<String>(); |
| remainder.addAll(value); |
| remainder.removeAll(contained); |
| valueName = toShortName.get(contained) + " + " + remainder; |
| } else { |
| valueName = value.toString(); |
| } |
| toShortName.put(value, shortName); |
| out.println(shortName + "\t" + valueName); |
| } |
| out.close(); |
| return toShortName; |
| } |
| |
| private static void showRawCoverage(Map<UnicodeSet, Set<String>> data) throws IOException { |
| System.out.println("\n***RAW COVERAGE (bridging unassigned)\n"); |
| PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "raw_coverage.txt"); |
| |
| for (UnicodeSet s : data.keySet()) { |
| Set<String> nameSet = data.get(s); |
| String name = nameSet.iterator().next(); |
| UnicodeSet bridged = new UnicodeSet(s).addBridges(DONT_CARE); |
| out.println(name + "\t" + s.size() + "\t" + bridged); |
| } |
| out.close(); |
| } |
| |
| private static UnicodeMap<Set<String>> showEquivalentCoverage(Map<UnicodeSet, Set<String>> data) throws IOException { |
| System.out.println("\n***EQUIVALENT COVERAGE\n"); |
| PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "equiv_coverage.txt"); |
| |
| UnicodeMap<Set<String>> map = new UnicodeMap<Set<String>>(); |
| |
| Map<String,Set<String>> nameToSingleton = new HashMap<String,Set<String>>(); |
| |
| for (UnicodeSet s : data.keySet()) { |
| Set<String> nameSet = data.get(s); |
| String name = nameSet.iterator().next(); |
| //System.out.println(s); |
| Set<String> temp2 = nameToSingleton.get(name); |
| if (temp2 == null) { |
| temp2 = new TreeSet<String>(English); |
| temp2.add(name); |
| } |
| map.composeWith(s, temp2, composer); |
| if (nameSet.size() > 1) { |
| TreeSet<String> temp = new TreeSet<String>(English); |
| temp.addAll(nameSet); |
| temp.remove(name); |
| out.println(name + "\t" + temp); |
| } |
| } |
| out.close(); |
| return map; |
| } |
| |
| private static void showSameGlyphs() throws IOException { |
| System.out.println("\n***Visual Equivalences"); |
| PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "same_glyphs.txt"); |
| PrintWriter out2 = BagFormatter.openUTF8Writer(outputDirectoryName, "same_glyphs.html"); |
| out2.println("<html><head>"); |
| out2.println("<meta content=\"text/html; charset=utf-8\" http-equiv=Content-Type></HEAD>"); |
| out2.println("<link rel='stylesheet' href='index.css' type='text/css'>"); |
| out2.println("</head><body><table>"); |
| HTMLTabber tabber = new HTMLTabber(); |
| |
| out2.println(tabber.process("Code1\tCode2\tNFC1\tNFC1\tCh1\tCh1\tCh1/F\tCh2/F\tName1\tName2\tFonts")); |
| tabber.setParameters(0, "class='c'"); |
| tabber.setParameters(1, "class='c'"); |
| tabber.setParameters(2, "class='nf'"); |
| tabber.setParameters(3, "class='nf'"); |
| tabber.setParameters(4, "class='p'"); |
| tabber.setParameters(5, "class='p'"); |
| //tabber.setParameters(6, "class='q'"); |
| //tabber.setParameters(7, "class='q'"); |
| tabber.setParameters(8, "class='n'"); |
| tabber.setParameters(9, "class='n'"); |
| tabber.setParameters(10, "class='f'"); |
| |
| for (R2<Integer,Integer> sample : equivalences.keySet()) { |
| final Set<String> reasonSet = equivalences.get(sample); |
| String reasons = reasonSet.toString(); |
| if (reasons.length() > 100) reasons = reasons.substring(0,100) + "..."; |
| final Integer codepoint1 = sample.get0(); |
| final Integer codepoint2 = sample.get1(); |
| |
| out.println("U+" + Utility.hex(codepoint1) + "\t" + "U+" + Utility.hex(codepoint2) |
| + "\t" + showNfc(codepoint1) + "\t" + showNfc(codepoint2) |
| + "\t" + showChar(codepoint1, false) + "\t" + showChar(codepoint2, false) |
| + "\t" + UCharacter.getExtendedName(codepoint1) + "\t" + UCharacter.getExtendedName(codepoint2) |
| + "\t" + reasons); |
| String line = "U+" + Utility.hex(codepoint1) + "\t" + "U+" + Utility.hex(codepoint2) |
| + "\t" + showNfc(codepoint1) + "\t" + showNfc(codepoint2) |
| + "\t" + showChar(codepoint1, false) + "\t" + showChar(codepoint2, true) |
| + "\t" + showChar(codepoint1, false) + "\t" + showChar(codepoint2, true) |
| + "\t" + UCharacter.getExtendedName(codepoint1) + "\t" + UCharacter.getExtendedName(codepoint2) |
| + "\t" + reasons; |
| |
| String fonts = "class='q' style='font-family:"; |
| int maxCount = 5; |
| for (String font : reasonSet) { |
| if (maxCount != 5) { |
| fonts += ","; |
| } |
| fonts += font; |
| --maxCount; |
| if (maxCount <= 0) break; |
| } |
| fonts += "'"; |
| tabber.setParameters(6, fonts); |
| tabber.setParameters(7, fonts); |
| out2.println(tabber.process(line)); |
| } |
| out2.println("</table></body>"); |
| out2.close(); |
| out.close(); |
| } |
| |
| private static void showInvisibles() throws IOException { |
| System.out.println("\n***Invisibles Equivalences"); |
| PrintWriter out = BagFormatter.openUTF8Writer(outputDirectoryName, "invisibles.txt"); |
| for (String sample : invisibles) { |
| String reasons = invisibles.get(sample).toString(); |
| if (reasons.length() > 100) reasons = reasons.substring(0,100) + "..."; |
| int codepoint = sample.codePointAt(0); |
| out.println("U+" + Utility.hex(sample) |
| + "\t" + showChar(codepoint, false) |
| + "\t" + showNfc(codepoint) |
| + "\t" + UCharacter.getExtendedName(codepoint) |
| + "\t" + reasons); |
| |
| } |
| out.close(); |
| } |
| |
| private static void getFontData(Matcher nameMatcher, Map<UnicodeSet, Set<String>> data, Map<String, Font> fontMap) { |
| GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); |
| Font[] fonts = env.getAllFonts(); |
| for (Font font : fonts) { |
| if (!font.isPlain()) { |
| continue; |
| } |
| String name = font.getName(); |
| int lastDash = name.lastIndexOf('-'); |
| String term = lastDash < 0 ? "" : name.substring(lastDash+1).toLowerCase(); |
| if (SKIP_TERMS.contains(term)) { |
| continue; |
| } |
| if (nameMatcher != null && !nameMatcher.reset(name).find()) { |
| continue; |
| } |
| fontMap.put(name,font); |
| } |
| for (String name : fontMap.keySet()) { |
| Font font = fontMap.get(name); |
| System.out.println(name); |
| UnicodeSet coverage = getCoverage(font); |
| Set<String> sameFonts = data.get(coverage); |
| if (sameFonts == null) { |
| data.put(coverage, sameFonts = new TreeSet<String>(SHORTER_FIRST)); |
| } else { |
| System.out.println("\tNote: same coverage as " + sameFonts.iterator().next()); |
| } |
| sameFonts.add(name); |
| } |
| } |
| |
| static Comparator<Integer> NFCLower = new Comparator<Integer>() { |
| public int compare(Integer o1, Integer o2) { |
| boolean n1 = Normalizer.isNormalized(o1, Normalizer.NFC, 0); |
| boolean n2 = Normalizer.isNormalized(o2, Normalizer.NFC, 0); |
| if (n1 != n2) return n1 ? -1 : 1; |
| n1 = Normalizer.isNormalized(o1, Normalizer.NFKC, 0); |
| n2 = Normalizer.isNormalized(o2, Normalizer.NFKC, 0); |
| if (n1 != n2) return n1 ? -1 : 1; |
| return o1.compareTo(o2); |
| } |
| }; |
| |
| static Comparator<R2<Integer,Integer>> NFCLowerR2 = new Comparator<R2<Integer,Integer>>() { |
| public int compare(R2<Integer, Integer> o1, R2<Integer, Integer> o2) { |
| int diff = NFCLower.compare(o1.get0(), o2.get0()); |
| if (diff != 0) return diff; |
| return NFCLower.compare(o1.get1(), o2.get1()); |
| } |
| }; |
| |
| private static String showNfc(int codepoint) { |
| return Normalizer.isNormalized(codepoint, Normalizer.NFC, 0) ? "" |
| : Normalizer.isNormalized(codepoint, Normalizer.NFKC, 0) ? "!C" : "!K"; |
| } |
| |
| private static String showChar(Integer item, boolean html) { |
| return rtlProtect(UTF16.valueOf(item), html); |
| } |
| static UnicodeSet RTL = new UnicodeSet("[[:bc=R:][:bc=AL:][:bc=AN:]]").freeze(); |
| static UnicodeSet CONTROLS = new UnicodeSet("[[:cc:][:Zl:][:Zp:]]").freeze(); |
| static UnicodeSet INVISIBLES = new UnicodeSet("[:di:]").freeze(); |
| static final char LRM = '\u200E'; |
| |
| private static String rtlProtect(String source, boolean html) { |
| if (CONTROLS.containsSome(source)) { |
| source = ""; |
| } else if (INVISIBLES.containsSome(source)) { |
| source = ""; |
| } else if (RTL.containsSome(source) || source.startsWith("\"")) { |
| source = LRM + source + LRM; |
| } |
| return html ? TransliteratorUtilities.toHTML.transform(source) : source; |
| } |
| |
| |
| private static Set<String> getLargestContained(Set<String> value, Collection<Set<String>> collection) { |
| Set<String> best = null; |
| for (Set<String> set : collection) { |
| if (best != null && best.size() > set.size()) { |
| continue; |
| } |
| if (value.containsAll(set)) { |
| best = set; |
| } |
| } |
| return best; |
| } |
| |
| private static String getShortAge(int i) { |
| String age = UCharacter.getAge(i).toString(); |
| return age.substring(0,age.indexOf('.',age.indexOf('.') + 1)); |
| } |
| |
| static SetMaker setMaker = new SetMaker() { |
| public Set make() { |
| return new TreeSet(); |
| } |
| }; |
| |
| static UnicodeMap<Set<String>> invisibles = new UnicodeMap(); |
| static Map<R2<Integer,Integer>, Set<String>> equivalences = new TreeMap<R2<Integer,Integer>, Set<String>>(NFCLowerR2); |
| // static Set<String> SKIP_SHAPES = new HashSet<String>(Arrays.asList( |
| // "MT-Extra", |
| // "JCsmPC", |
| // "DFKaiShu-SB-Estd-BF", |
| // "LiGothicMed", |
| // "LiHeiPro", |
| // "LiSongPro", |
| // "LiSungLight", |
| // "PMingLiU", |
| // "SIL-Hei-Med-Jian", |
| // "SIL-Kai-Reg-Jian", |
| // "CharcoalCY", |
| // "GenevaCY", |
| // "HelveticaCYBoldOblique", |
| // "HelveticaCYOblique", |
| // "HelveticaCYPlain", |
| // "HoeflerText-Ornaments", |
| // "Apple-Chancery", |
| // "MSReferenceSpecialty", |
| // "Stencil", |
| // "Hooge0555", |
| // "Hooge0556", |
| // "Desdemona", |
| // "EccentricStd", |
| // "EngraversMT", |
| // "MesquiteStd", |
| // "RosewoodStd-Fill", |
| // "Stencil", |
| // "StencilStd", |
| // "Osaka", |
| // "Osaka-Mono", |
| // "Kroeger0455", |
| // "Kroeger0456", |
| // "Uni0563", |
| // "Uni0564", |
| // "Code2001", |
| // "AppleSymbols", |
| // "AppleGothic", |
| // "AppleMyungjo", |
| // "JCkg", |
| // "MalithiWeb", |
| // "JCfg" |
| // )); |
| |
| // bug on Mac: http://forums.sun.com/thread.jspa?threadID=5209611 |
| private static UnicodeSet getCoverage(Font font) { |
| String name = font.getFontName(); |
| boolean skipShapes = SKIP_SHAPES.contains(name); |
| UnicodeSet result = new UnicodeSet(); |
| final FontRenderContext fontRenderContext = new FontRenderContext(null, false, false); |
| char[] array = new char[1]; |
| char[] array2 = new char[2]; |
| Map<Rectangle2D,Map<Shape,UnicodeSet>> boundsToData = new TreeMap<Rectangle2D,Map<Shape,UnicodeSet>>(ShapeComparator); |
| for (UnicodeSetIterator it = new UnicodeSetIterator(COVERAGE); it.next();) { |
| if (font.canDisplay(it.codepoint)) { |
| char[] temp; |
| if (it.codepoint <= 0xFFFF) { |
| array[0] = (char) it.codepoint; |
| temp = array; |
| } else { |
| Character.toChars(it.codepoint, array2, 0); |
| temp = array2; |
| } |
| |
| GlyphVector glyphVector = font.createGlyphVector(fontRenderContext, temp); |
| int glyphCode = glyphVector.getGlyphCode(0); |
| boolean validchar = (glyphCode > 0); |
| if (!validchar) continue; |
| |
| result.add(it.codepoint); |
| |
| if (skipShapes) continue; |
| Shape shape = glyphVector.getOutline(); |
| if (isInvisible(shape)) { |
| Set<String> set = invisibles.get(it.codepoint); |
| if (set == null) { |
| invisibles.put(it.codepoint, set = new TreeSet<String>()); |
| } |
| set.add(name); |
| } else { |
| Rectangle2D bounds = glyphVector.getVisualBounds(); |
| Map<Shape, UnicodeSet> map = boundsToData.get(bounds); |
| if (map == null) { |
| boundsToData.put(bounds, map = new TreeMap<Shape,UnicodeSet>(ShapeComparator)); |
| } |
| UnicodeSet set = map.get(shape); |
| if (set == null) { |
| map.put(shape, set = new UnicodeSet()); |
| } |
| if (false && set.size() != 0) { |
| System.out.println("Adding " + Utility.hex(it.codepoint) + "\t" + UTF16.valueOf(it.codepoint) + "\tto " + set.toPattern(false)); |
| } |
| set.add(it.codepoint); |
| } |
| } |
| } |
| //System.out.println(result.size() + "\t" + result); |
| for (Rectangle2D bounds : boundsToData.keySet()) { |
| Map<Shape, UnicodeSet> map = boundsToData.get(bounds); |
| for (Shape shape : map.keySet()) { |
| UnicodeSet set = map.get(shape); |
| set.removeAll(CONTROLS); |
| if (set.size() != 1) { |
| //System.out.println(set.toPattern(false)); |
| for (UnicodeSetIterator it = new UnicodeSetIterator(set); it.next();) { |
| for (UnicodeSetIterator it2 = new UnicodeSetIterator(set); it2.next();) { |
| int cp = it.codepoint; |
| int cp2 = it2.codepoint; |
| if (cp >= cp2) continue; |
| R2<Integer, Integer> r = Row.of(cp, cp2); |
| Set<String> reasons = equivalences.get(r); |
| if (reasons == null) { |
| equivalences.put(r, reasons = new TreeSet()); |
| } |
| reasons.add(name); |
| } |
| } |
| } |
| } |
| } |
| return result.freeze(); |
| } |
| |
| static Comparator<Rectangle2D> RectComparator = new Comparator<Rectangle2D>() { |
| |
| public int compare(Rectangle2D r1, Rectangle2D r2) { |
| int diff; |
| if (0 != (diff = compareDiff(r1.getX(),r2.getX()))) return diff; |
| if (0 != (diff = compareDiff(r1.getY(),r2.getY()))) return diff; |
| if (0 != (diff = compareDiff(r1.getWidth(),r2.getWidth()))) return diff; |
| if (0 != (diff = compareDiff(r1.getHeight(),r2.getHeight()))) return diff; |
| return 0; |
| } |
| |
| }; |
| |
| static final AffineTransform IDENTITY = new AffineTransform(); |
| |
| static boolean isInvisible(Shape shape) { |
| return shape.getPathIterator(IDENTITY).isDone(); |
| } |
| |
| static Comparator<Shape> ShapeComparator = new Comparator<Shape>() { |
| float[] coords1 = new float[6]; |
| float[] coords2 = new float[6]; |
| |
| public int compare(Shape s1, Shape s2) { |
| int diff; |
| PathIterator p1 = s1.getPathIterator(IDENTITY); |
| PathIterator p2 = s2.getPathIterator(IDENTITY); |
| while (true) { |
| if (p1.isDone()) { |
| return p2.isDone() ? 0 : -1; |
| } else if (p2.isDone()) { |
| return 1; |
| } |
| int t1 = p1.currentSegment(coords1); |
| int t2 = p2.currentSegment(coords2); |
| diff = t1 - t2; |
| if (diff != 0) return diff; |
| /* |
| * SEG_MOVETO and SEG_LINETO types returns one point, |
| * SEG_QUADTO returns two points, |
| * SEG_CUBICTO returns 3 points |
| * and SEG_CLOSE does not return any points. |
| */ |
| switch (t1) { |
| case PathIterator.SEG_CUBICTO: |
| if (0 != (diff = compareDiff(coords1[5],coords2[5]))) return diff; |
| if (0 != (diff = compareDiff(coords1[4],coords2[4]))) return diff; |
| case PathIterator.SEG_QUADTO: |
| if (0 != (diff = compareDiff(coords1[3],coords2[3]))) return diff; |
| if (0 != (diff = compareDiff(coords1[2],coords2[2]))) return diff; |
| case PathIterator.SEG_MOVETO: |
| case PathIterator.SEG_LINETO: |
| if (0 != (diff = compareDiff(coords1[1],coords2[1]))) return diff; |
| if (0 != (diff = compareDiff(coords1[0],coords2[0]))) return diff; |
| case PathIterator.SEG_CLOSE: break; |
| default: throw new IllegalArgumentException(); |
| } |
| p1.next(); |
| p2.next(); |
| } |
| } |
| }; |
| |
| private static int compareDiff(float f, float g) { |
| return f < g ? -1 : f > g ? 1 : 0; |
| } |
| private static int compareDiff(double f, double g) { |
| return f < g ? -1 : f > g ? 1 : 0; |
| } |
| } |