]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_4_2-src/tools/misc/src/com/ibm/icu/dev/tool/cldr/CheckSystemFonts.java
go
[Dictionary.git] / jars / icu4j-4_4_2-src / tools / misc / src / com / ibm / icu / dev / tool / cldr / CheckSystemFonts.java
1 /*\r
2  **********************************************************************\r
3  * Copyright (c) 2009-2010, Google, International Business Machines\r
4  * Corporation and others.  All Rights Reserved.\r
5  **********************************************************************\r
6  * Author: Mark Davis\r
7  **********************************************************************\r
8  */\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
33 \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
51 \r
52 \r
53 public class CheckSystemFonts {\r
54     \r
55     static String outputDirectoryName;\r
56     static Set<String> SKIP_SHAPES = new HashSet<String>();\r
57     \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
64                     );\r
65         }\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
71         }\r
72         loadSkipShapes();\r
73 \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
77         \r
78         showInvisibles();\r
79         showSameGlyphs();\r
80 \r
81         UnicodeMap<Set<String>> map = showEquivalentCoverage(data);\r
82 \r
83         showRawCoverage(data);\r
84 \r
85         Map<Set<String>, String> toShortName = showRawCoverage(map);\r
86 \r
87         showFullCoverage(map, toShortName);\r
88     }\r
89 \r
90     private static void loadSkipShapes() {\r
91         try {\r
92             BufferedReader in = BagFormatter.openUTF8Reader(outputDirectoryName, "skip_fonts.txt");\r
93             while (true) {\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
99                 }\r
100             }\r
101             in.close();\r
102         } catch (IOException e) {\r
103             System.err.println("Couldn't open:\t" + outputDirectoryName + "/" + "skip_fonts.txt");\r
104         }\r
105     }\r
106 \r
107 \r
108     private static final Collator English = Collator.getInstance();\r
109 \r
110     static {\r
111         English.setStrength(Collator.SECONDARY);\r
112     }\r
113 \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
116 \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
122         }\r
123     };\r
124 \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
130         }\r
131     };\r
132 \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
138         }\r
139     };\r
140 \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
145     ));\r
146 \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
152                             : intern(a,b);\r
153         }\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
159                 result.addAll(a);\r
160                 result.addAll(b);\r
161                 cache.put(row, result);\r
162             }\r
163             return result;\r
164         }\r
165     };\r
166 \r
167 \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
171 \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
178             }\r
179             out.println(codes + "\t" + toShortName.get(map.get(it.codepoint)) + "\t" + names);\r
180         }\r
181 \r
182         UnicodeSet missing = new UnicodeSet(COVERAGE).removeAll(map.keySet());\r
183         out.println("\nMISSING:\t" + missing.size() + "\n");\r
184 \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
188         }\r
189 \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
200                 }\r
201                 out.println(codes + "\t" + value + "\t" + names);\r
202             }\r
203             out.println();\r
204         }\r
205         out.close();\r
206     }\r
207 \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
211 \r
212         int count = 0;\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
219             String valueName;\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
225             } else {\r
226                 valueName = value.toString();\r
227             }\r
228             toShortName.put(value, shortName);\r
229             out.println(shortName + "\t" + valueName);\r
230         }\r
231         out.close();\r
232         return toShortName;\r
233     }\r
234 \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
238 \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
244         }\r
245         out.close();\r
246     }\r
247 \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
251 \r
252         UnicodeMap<Set<String>> map = new UnicodeMap<Set<String>>();\r
253 \r
254         Map<String,Set<String>> nameToSingleton = new HashMap<String,Set<String>>();\r
255 \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
263                 temp2.add(name);\r
264             }\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
269                 temp.remove(name);\r
270                 out.println(name + "\t" + temp);\r
271             }\r
272         }\r
273         out.close();\r
274         return map;\r
275     }\r
276 \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
286 \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
299 \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
306             \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
311                     + "\t" + reasons);\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
317                     + "\t" + reasons;\r
318             \r
319             String fonts = "class='q' style='font-family:";\r
320             int maxCount = 5;\r
321             for (String font : reasonSet) {\r
322                 if (maxCount != 5) {\r
323                     fonts += ",";\r
324                 }\r
325                 fonts += font;\r
326                 --maxCount;\r
327                 if (maxCount <= 0) break;\r
328             }\r
329             fonts += "'";\r
330             tabber.setParameters(6, fonts);\r
331             tabber.setParameters(7, fonts);\r
332             out2.println(tabber.process(line));\r
333         }\r
334         out2.println("</table></body>");\r
335         out2.close();\r
336         out.close();\r
337     }\r
338 \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
350                     + "\t" + reasons);\r
351 \r
352         }\r
353         out.close();\r
354     }\r
355 \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
361                 continue;\r
362             }\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
367                 continue;\r
368             }\r
369             if (nameMatcher != null && !nameMatcher.reset(name).find()) {\r
370                 continue;\r
371             }\r
372             fontMap.put(name,font);\r
373         }\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
381             } else {\r
382                 System.out.println("\tNote: same coverage as " + sameFonts.iterator().next());\r
383             }\r
384             sameFonts.add(name);\r
385         }\r
386     }\r
387     \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
397         } \r
398     };\r
399     \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
405         }\r
406     };\r
407 \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
411     }\r
412 \r
413     private static String showChar(Integer item, boolean html) {\r
414         return rtlProtect(UTF16.valueOf(item), html);\r
415     }\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
420 \r
421     private static String rtlProtect(String source, boolean html) {\r
422         if (CONTROLS.containsSome(source)) {\r
423             source = "";\r
424         } else if (INVISIBLES.containsSome(source)) {\r
425             source = "";\r
426         } else if (RTL.containsSome(source) || source.startsWith("\"")) {\r
427             source = LRM + source + LRM;\r
428         }\r
429         return html ? TransliteratorUtilities.toHTML.transform(source) : source;\r
430     }\r
431 \r
432 \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
437                 continue;\r
438             }\r
439             if (value.containsAll(set)) {\r
440                 best = set;\r
441             }\r
442         }\r
443         return best;\r
444     }\r
445 \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
449     }\r
450 \r
451     static SetMaker setMaker = new SetMaker() {\r
452         public Set make() {\r
453             return new TreeSet();\r
454         } \r
455     };\r
456 \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
460 //            "MT-Extra",\r
461 //            "JCsmPC",\r
462 //            "DFKaiShu-SB-Estd-BF",\r
463 //            "LiGothicMed",\r
464 //            "LiHeiPro",\r
465 //            "LiSongPro",\r
466 //            "LiSungLight",\r
467 //            "PMingLiU",\r
468 //            "SIL-Hei-Med-Jian",\r
469 //            "SIL-Kai-Reg-Jian",\r
470 //            "CharcoalCY",\r
471 //            "GenevaCY",\r
472 //            "HelveticaCYBoldOblique",\r
473 //            "HelveticaCYOblique",\r
474 //            "HelveticaCYPlain",\r
475 //            "HoeflerText-Ornaments",\r
476 //            "Apple-Chancery",\r
477 //            "MSReferenceSpecialty",\r
478 //            "Stencil",\r
479 //            "Hooge0555",\r
480 //            "Hooge0556",\r
481 //            "Desdemona",\r
482 //            "EccentricStd",\r
483 //            "EngraversMT",\r
484 //            "MesquiteStd",\r
485 //            "RosewoodStd-Fill",\r
486 //            "Stencil",\r
487 //            "StencilStd",\r
488 //            "Osaka",\r
489 //            "Osaka-Mono",\r
490 //            "Kroeger0455",\r
491 //            "Kroeger0456",\r
492 //            "Uni0563",\r
493 //            "Uni0564",\r
494 //            "Code2001",\r
495 //            "AppleSymbols",\r
496 //            "AppleGothic", \r
497 //            "AppleMyungjo",\r
498 //            "JCkg",\r
499 //            "MalithiWeb",\r
500 //            "JCfg"\r
501 //    ));\r
502 \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
514                 char[] temp;\r
515                 if (it.codepoint <= 0xFFFF) {\r
516                     array[0] = (char) it.codepoint;\r
517                     temp = array;\r
518                 } else {\r
519                     Character.toChars(it.codepoint, array2, 0);\r
520                     temp = array2;\r
521                 }\r
522 \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
527 \r
528                 result.add(it.codepoint);\r
529 \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
534                     if (set == null) {\r
535                         invisibles.put(it.codepoint, set = new TreeSet<String>());\r
536                     }\r
537                     set.add(name);\r
538                 } else {\r
539                     Rectangle2D bounds = glyphVector.getVisualBounds();\r
540                     Map<Shape, UnicodeSet> map = boundsToData.get(bounds);\r
541                     if (map == null) {\r
542                         boundsToData.put(bounds, map = new TreeMap<Shape,UnicodeSet>(ShapeComparator));\r
543                     }\r
544                     UnicodeSet set = map.get(shape);\r
545                     if (set == null) {\r
546                         map.put(shape, set = new UnicodeSet());\r
547                     }\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
550                     }\r
551                     set.add(it.codepoint);\r
552                 }\r
553             }\r
554         }\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
572                             }\r
573                             reasons.add(name);\r
574                         }\r
575                     }\r
576                 }\r
577             }\r
578         }\r
579         return result.freeze();\r
580     }\r
581 \r
582     static Comparator<Rectangle2D> RectComparator = new Comparator<Rectangle2D>() {\r
583 \r
584         public int compare(Rectangle2D r1, Rectangle2D r2) {\r
585             int diff;\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
590             return 0;\r
591         }\r
592 \r
593     };\r
594 \r
595     static final AffineTransform IDENTITY = new AffineTransform();\r
596 \r
597     static boolean isInvisible(Shape shape) {\r
598         return shape.getPathIterator(IDENTITY).isDone();\r
599     }\r
600 \r
601     static Comparator<Shape> ShapeComparator = new Comparator<Shape>() {\r
602         float[] coords1 = new float[6];\r
603         float[] coords2 = new float[6];\r
604 \r
605         public int compare(Shape s1, Shape s2) {\r
606             int diff;\r
607             PathIterator p1 = s1.getPathIterator(IDENTITY);\r
608             PathIterator p2 = s2.getPathIterator(IDENTITY);\r
609             while (true) {\r
610                 if (p1.isDone()) {\r
611                     return p2.isDone() ? 0 : -1;\r
612                 } else if (p2.isDone()) {\r
613                     return 1;\r
614                 }\r
615                 int t1 = p1.currentSegment(coords1);\r
616                 int t2 = p2.currentSegment(coords2);\r
617                 diff = t1 - t2;\r
618                 if (diff != 0) return diff;\r
619                 /*\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
624                  */\r
625                 switch (t1) {\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
638                 }\r
639                 p1.next();\r
640                 p2.next();\r
641             }\r
642         }\r
643     };\r
644 \r
645     private static int compareDiff(float f, float g) {\r
646         return f < g ? -1 : f > g ? 1 : 0;\r
647     }\r
648     private static int compareDiff(double f, double g) {\r
649         return f < g ? -1 : f > g ? 1 : 0;\r
650     }\r
651 }\r