4 * License : The MIT License
5 * Copyright(c) 2012 olyutorskii
8 package jp.sfjp.jindolf.glyph;
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;
26 * フォント環境に関する情報あれこれをバックグラウンドで収集する。
29 * <li>与えられた選択肢から利用可能なフォントを一つ選ぶこと
30 * <li>任意の文字列を表示可能な全フォントを列挙すること
34 * この二つをバックグラウンドで非同期に収集する。
38 * 各種フォント環境収集メソッドの遅さをカバーするのが実装目的。
41 public class FontEnv {
46 public static final FontEnv DEFAULT;
48 /** {@link java.awt.Font#DIALOG} 代替品。 */
49 private static final String FAMILY_DIALOG = "Dialog";
52 private static final String[] INIT_FAMILY_NAMES = {
53 "Hiragino Kaku Gothic Pro", // for MacOS X
54 "Hiragino Kaku Gothic Std",
56 "MS PGothic", // for WinXP
62 /** JIS X0208:1990 表示確認用文字列。 */
63 private static final String JPCHECK_CODE = "あ凜熙峠ゑアアヴヰ┼ЖΩ9A";
65 /** {@link java.util.Locale#ROOT} 代替品。 */
66 private static final Locale LOCALE_ROOT = new Locale("", "", "");
68 private static final int POOL_SZ = 2;
69 private static final int STRIDE = 15;
72 DEFAULT = new FontEnv(JPCHECK_CODE, INIT_FAMILY_NAMES);
76 private final String proveChars;
77 private final List<String> fontFamilyList;
79 private final Future<List<String>> listLoadFuture;
80 private final Future<String> fontSelectFuture;
85 * 完了と同時に裏でフォント情報収集タスクが走る。
86 * @param proveChars 表示判定用文字列
87 * @param fontFamilyList フォント候補
88 * @throws NullPointerException 引数がnull
90 public FontEnv(String proveChars, List<String> fontFamilyList)
91 throws NullPointerException {
94 if(proveChars == null || fontFamilyList == null){
95 throw new NullPointerException();
98 this.proveChars = proveChars;
99 this.fontFamilyList = fontFamilyList;
101 ExecutorService service = Executors.newFixedThreadPool(POOL_SZ);
103 Callable<List<String>> loadTask = new FontListLoader();
104 this.listLoadFuture = service.submit(loadTask);
106 Callable<String> selectTask = new FontSelector();
107 this.fontSelectFuture = service.submit(selectTask);
116 * 完了と同時に裏でフォント情報収集タスクが走る。
117 * @param proveChars 表示判定用文字列
118 * @param fontFamilyList フォント候補
119 * @throws NullPointerException 引数がnull
121 public FontEnv(String proveChars, String ... fontFamilyList)
122 throws NullPointerException {
123 this(proveChars, Arrays.asList(fontFamilyList));
131 @SuppressWarnings("CallToThreadYield")
132 protected static void yield(){
138 * 指定文字列が表示可能なフォントファミリ集合を生成する。
140 * <p>結構実行時間がかかるかも(数千ms)。乱用禁物。
142 * @param checkChars テスト対象の文字が含まれた文字列
145 protected static Collection<String> createFontSet(String checkChars){
146 GraphicsEnvironment ge =
147 GraphicsEnvironment.getLocalGraphicsEnvironment();
148 Font[] allFonts = ge.getAllFonts();
152 Collection<String> result = new HashSet<>();
154 for(Font font : allFonts){
155 if(++ct % STRIDE == 0) yield();
157 String familyName = font.getFamily();
158 if(result.contains(familyName)) continue;
159 if(font.canDisplayUpTo(checkChars) >= 0) continue;
161 result.add(familyName);
168 * システムに存在する有効なファミリ名か判定する。
169 * @param familyName フォントファミリ名。
170 * @return 存在する有効なファミリ名ならtrue
172 protected static boolean isValidFamilyName(String familyName){
173 int style = 0x00 | Font.PLAIN;
175 Font dummyFont = new Font(familyName, style, size);
177 String dummyFamilyName = dummyFont.getFamily(LOCALE_ROOT);
178 if(dummyFamilyName.equals(familyName)) return true;
180 String dummyLocalFamilyName = dummyFont.getFamily();
181 if(dummyLocalFamilyName.equals(familyName)) return true;
187 * 複数の候補から利用可能なフォントを一つ選び、生成する。
188 * 候補から適当なファミリが見つからなかったら"Dialog"が選択される。
189 * @param fontList フォントファミリ名候補
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;
201 return defaultFamilyName;
207 * @return フォントファミリー名のリスト
208 * @throws IllegalStateException 収集タスクに異常が発生
210 public List<String> getFontFamilyList() throws IllegalStateException {
214 result = this.listLoadFuture.get();
215 }catch(ExecutionException e){
216 throw new IllegalStateException(e);
217 }catch(InterruptedException e){
218 throw new IllegalStateException(e);
227 * @throws IllegalStateException 収集タスクに異常が発生
229 public String selectFontFamily() throws IllegalStateException {
233 result = this.fontSelectFuture.get();
234 }catch(ExecutionException e){
235 throw new IllegalStateException(e);
236 }catch(InterruptedException e){
237 throw new IllegalStateException(e);
247 protected final class FontListLoader implements Callable<List<String>> {
252 private FontListLoader(){
259 * @return {@inheritDoc}
262 public List<String> call(){
263 Collection<String> fontSet =
264 createFontSet(FontEnv.this.proveChars);
267 List<String> result = new ArrayList<>(fontSet);
268 Collections.sort(result);
271 result = Collections.unmodifiableList(result);
280 protected final class FontSelector implements Callable<String> {
285 private FontSelector(){
292 * @return {@inheritDoc}
295 public String call(){
296 String result = availableFontFamily(FontEnv.this.fontFamilyList);