OSDN Git Service

use JRE1.6 runtime static members
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / glyph / FontEnv.java
1 /*
2  * font environment
3  *
4  * License : The MIT License
5  * Copyright(c) 2012 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.glyph;
9
10 import java.awt.Font;
11 import java.awt.GraphicsEnvironment;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Locale;
19 import java.util.concurrent.Callable;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.ExecutorService;
22 import java.util.concurrent.Executors;
23 import java.util.concurrent.Future;
24
25 /**
26  * フォント環境に関する情報あれこれをバックグラウンドで収集する。
27  *
28  * <ul>
29  * <li>与えられた選択肢から利用可能なフォントを一つ選ぶこと
30  * <li>任意の文字列を表示可能な全フォントを列挙すること
31  * </ul>
32  *
33  * <p>この二つをバックグラウンドで非同期に収集する。
34  *
35  * <p>各種フォント環境収集メソッドの遅さをカバーするのが実装目的。
36  */
37 public class FontEnv {
38
39     /**
40      * デフォルトのフォント環境。
41      */
42     public static final FontEnv DEFAULT;
43
44     /** フォントファミリ選択肢。 */
45     private static final String[] INIT_FAMILY_NAMES = {
46         "Hiragino Kaku Gothic Pro",  // for MacOS X
47         "Hiragino Kaku Gothic Std",
48         "Osaka",
49         "MS PGothic",                // for WinXP
50         "MS Gothic",
51         "IPAMonaPGothic",
52         // TODO X11用のおすすめは?
53     };
54
55     /** JIS X0208:1990 表示確認用文字列。 */
56     private static final String JPCHECK_CODE = "あ凜熙峠ゑアアヴヰ┼ЖΩ9A";
57
58     private static final int POOL_SZ = 2;
59     private static final int STRIDE = 15;
60
61     static{
62         DEFAULT = new FontEnv(JPCHECK_CODE, INIT_FAMILY_NAMES);
63     }
64
65
66     private final String proveChars;
67     private final List<String> fontFamilyList;
68
69     private final Future<List<String>> listLoadFuture;
70     private final Future<String>       fontSelectFuture;
71
72
73     /**
74      * コンストラクタ。
75      *
76      * <p>完了と同時に裏でフォント情報収集タスクが走る。
77      *
78      * @param proveChars 表示判定用文字列
79      * @param fontFamilyList フォント候補
80      * @throws NullPointerException 引数がnull
81      */
82     public FontEnv(String proveChars, List<String> fontFamilyList)
83             throws NullPointerException {
84         super();
85
86         if(proveChars == null || fontFamilyList == null){
87             throw new NullPointerException();
88         }
89
90         this.proveChars = proveChars;
91         this.fontFamilyList = fontFamilyList;
92
93         ExecutorService service = Executors.newFixedThreadPool(POOL_SZ);
94
95         Callable<List<String>> loadTask = new FontListLoader();
96         this.listLoadFuture = service.submit(loadTask);
97
98         Callable<String> selectTask = new FontSelector();
99         this.fontSelectFuture = service.submit(selectTask);
100
101         service.shutdown();
102
103         return;
104     }
105
106     /**
107      * コンストラクタ。
108      *
109      * <p>完了と同時に裏でフォント情報収集タスクが走る。
110      *
111      * @param proveChars 表示判定用文字列
112      * @param fontFamilyList フォント候補
113      * @throws NullPointerException 引数がnull
114      */
115     public FontEnv(String proveChars, String ... fontFamilyList)
116             throws NullPointerException {
117         this(proveChars, Arrays.asList(fontFamilyList));
118         return;
119     }
120
121
122     /**
123      * 自発的なスケジューリングを促す。
124      */
125     @SuppressWarnings("CallToThreadYield")
126     protected static void yield(){
127         Thread.yield();
128         return;
129     }
130
131     /**
132      * 指定文字列が表示可能なフォントファミリ集合を生成する。
133      *
134      * <p>結構実行時間がかかるかも(数千ms)。乱用禁物。
135      *
136      * @param checkChars テスト対象の文字が含まれた文字列
137      * @return フォント集合
138      */
139     protected static Collection<String> createFontSet(String checkChars){
140         GraphicsEnvironment ge =
141                 GraphicsEnvironment.getLocalGraphicsEnvironment();
142         Font[] allFonts = ge.getAllFonts();
143
144         yield();
145
146         Collection<String> result = new HashSet<>();
147         int ct = 0;
148         for(Font font : allFonts){
149             if(++ct % STRIDE == 0) yield();
150
151             String familyName = font.getFamily();
152             if(result.contains(familyName)) continue;
153             if(font.canDisplayUpTo(checkChars) >= 0) continue;
154
155             result.add(familyName);
156         }
157
158         return result;
159     }
160
161     /**
162      * システムに存在する有効なファミリ名か判定する。
163      *
164      * @param familyName フォントファミリ名。
165      * @return 存在する有効なファミリ名ならtrue
166      */
167     protected static boolean isValidFamilyName(String familyName){
168         int style = 0x00 | Font.PLAIN;
169         int size = 1;
170         Font dummyFont = new Font(familyName, style, size);
171
172         String dummyFamilyName = dummyFont.getFamily(Locale.ROOT);
173         if(dummyFamilyName.equals(familyName)) return true;
174
175         String dummyLocalFamilyName = dummyFont.getFamily();
176         if(dummyLocalFamilyName.equals(familyName)) return true;
177
178         return false;
179     }
180
181     /**
182      * 複数の候補から利用可能なフォントを一つ選び、生成する。
183      *
184      * <p>候補から適当なファミリが見つからなかったら"Dialog"が選択される。
185      *
186      * @param fontList フォントファミリ名候補
187      * @return フォント
188      */
189     protected static String availableFontFamily(Iterable<String> fontList){
190         String defaultFamilyName = Font.DIALOG;
191         for(String familyName : fontList){
192             if(isValidFamilyName(familyName)){
193                 defaultFamilyName = familyName;
194                 break;
195             }
196         }
197
198         return defaultFamilyName;
199     }
200
201
202     /**
203      * フォントファミリー名のリストを返す。
204      *
205      * @return フォントファミリー名のリスト
206      * @throws IllegalStateException 収集タスクに異常が発生
207      */
208     public List<String> getFontFamilyList() throws IllegalStateException {
209         List<String> result;
210
211         try{
212             result = this.listLoadFuture.get();
213         }catch(ExecutionException | InterruptedException e){
214             throw new IllegalStateException(e);
215         }
216
217         return result;
218     }
219
220     /**
221      * 利用可能なフォントファミリ名を返す。
222      *
223      * @return フォントファミリー名
224      * @throws IllegalStateException 収集タスクに異常が発生
225      */
226     public String selectFontFamily() throws IllegalStateException {
227         String result;
228
229         try{
230             result = this.fontSelectFuture.get();
231         }catch(ExecutionException | InterruptedException e){
232             throw new IllegalStateException(e);
233         }
234
235         return result;
236     }
237
238
239     /**
240      * フォントリスト収集タスク。
241      */
242     protected final class FontListLoader implements Callable<List<String>> {
243
244         /**
245          * コンストラクタ。
246          */
247         private FontListLoader(){
248             super();
249             return;
250         }
251
252         /**
253          * {@inheritDoc}
254          *
255          * @return {@inheritDoc}
256          */
257         @Override
258         public List<String> call(){
259             Collection<String> fontSet =
260                     createFontSet(FontEnv.this.proveChars);
261             yield();
262
263             List<String> result = new ArrayList<>(fontSet);
264             Collections.sort(result);
265             yield();
266
267             result = Collections.unmodifiableList(result);
268
269             return result;
270         }
271     }
272
273     /**
274      * フォント選択タスク。
275      */
276     protected final class FontSelector implements Callable<String> {
277
278         /**
279          * コンストラクタ。
280          */
281         private FontSelector(){
282             super();
283             return;
284         }
285
286         /**
287          * {@inheritDoc}
288          *
289          * @return {@inheritDoc}
290          */
291         @Override
292         public String call(){
293             String result = availableFontFamily(FontEnv.this.fontFamilyList);
294             return result;
295         }
296     }
297
298 }