OSDN Git Service

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