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>任意の文字列を表示可能な全フォントを列挙すること
33 * <p>この二つをバックグラウンドで非同期に収集する。
35 * <p>各種フォント環境収集メソッドの遅さをカバーするのが実装目的。
37 public class FontEnv {
42 public static final FontEnv DEFAULT;
45 private static final String[] INIT_FAMILY_NAMES = {
46 "Hiragino Kaku Gothic Pro", // for MacOS X
47 "Hiragino Kaku Gothic Std",
49 "MS PGothic", // for WinXP
55 /** JIS X0208:1990 表示確認用文字列。 */
56 private static final String JPCHECK_CODE = "あ凜熙峠ゑアアヴヰ┼ЖΩ9A";
58 private static final int POOL_SZ = 2;
59 private static final int STRIDE = 15;
62 DEFAULT = new FontEnv(JPCHECK_CODE, INIT_FAMILY_NAMES);
66 private final String proveChars;
67 private final List<String> fontFamilyList;
69 private final Future<List<String>> listLoadFuture;
70 private final Future<String> fontSelectFuture;
76 * <p>完了と同時に裏でフォント情報収集タスクが走る。
78 * @param proveChars 表示判定用文字列
79 * @param fontFamilyList フォント候補
80 * @throws NullPointerException 引数がnull
82 public FontEnv(String proveChars, List<String> fontFamilyList)
83 throws NullPointerException {
86 if(proveChars == null || fontFamilyList == null){
87 throw new NullPointerException();
90 this.proveChars = proveChars;
91 this.fontFamilyList = fontFamilyList;
93 ExecutorService service = Executors.newFixedThreadPool(POOL_SZ);
95 Callable<List<String>> loadTask = new FontListLoader();
96 this.listLoadFuture = service.submit(loadTask);
98 Callable<String> selectTask = new FontSelector();
99 this.fontSelectFuture = service.submit(selectTask);
109 * <p>完了と同時に裏でフォント情報収集タスクが走る。
111 * @param proveChars 表示判定用文字列
112 * @param fontFamilyList フォント候補
113 * @throws NullPointerException 引数がnull
115 public FontEnv(String proveChars, String ... fontFamilyList)
116 throws NullPointerException {
117 this(proveChars, Arrays.asList(fontFamilyList));
125 @SuppressWarnings("CallToThreadYield")
126 protected static void yield(){
132 * 指定文字列が表示可能なフォントファミリ集合を生成する。
134 * <p>結構実行時間がかかるかも(数千ms)。乱用禁物。
136 * @param checkChars テスト対象の文字が含まれた文字列
139 protected static Collection<String> createFontSet(String checkChars){
140 GraphicsEnvironment ge =
141 GraphicsEnvironment.getLocalGraphicsEnvironment();
142 Font[] allFonts = ge.getAllFonts();
146 Collection<String> result = new HashSet<>();
148 for(Font font : allFonts){
149 if(++ct % STRIDE == 0) yield();
151 String familyName = font.getFamily();
152 if(result.contains(familyName)) continue;
153 if(font.canDisplayUpTo(checkChars) >= 0) continue;
155 result.add(familyName);
162 * システムに存在する有効なファミリ名か判定する。
164 * @param familyName フォントファミリ名。
165 * @return 存在する有効なファミリ名ならtrue
167 protected static boolean isValidFamilyName(String familyName){
168 int style = 0x00 | Font.PLAIN;
170 Font dummyFont = new Font(familyName, style, size);
172 String dummyFamilyName = dummyFont.getFamily(Locale.ROOT);
173 if(dummyFamilyName.equals(familyName)) return true;
175 String dummyLocalFamilyName = dummyFont.getFamily();
176 if(dummyLocalFamilyName.equals(familyName)) return true;
182 * 複数の候補から利用可能なフォントを一つ選び、生成する。
184 * <p>候補から適当なファミリが見つからなかったら"Dialog"が選択される。
186 * @param fontList フォントファミリ名候補
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;
198 return defaultFamilyName;
205 * @return フォントファミリー名のリスト
206 * @throws IllegalStateException 収集タスクに異常が発生
208 public List<String> getFontFamilyList() throws IllegalStateException {
212 result = this.listLoadFuture.get();
213 }catch(ExecutionException | InterruptedException e){
214 throw new IllegalStateException(e);
224 * @throws IllegalStateException 収集タスクに異常が発生
226 public String selectFontFamily() throws IllegalStateException {
230 result = this.fontSelectFuture.get();
231 }catch(ExecutionException | InterruptedException e){
232 throw new IllegalStateException(e);
242 protected final class FontListLoader implements Callable<List<String>> {
247 private FontListLoader(){
255 * @return {@inheritDoc}
258 public List<String> call(){
259 Collection<String> fontSet =
260 createFontSet(FontEnv.this.proveChars);
263 List<String> result = new ArrayList<>(fontSet);
264 Collections.sort(result);
267 result = Collections.unmodifiableList(result);
276 protected final class FontSelector implements Callable<String> {
281 private FontSelector(){
289 * @return {@inheritDoc}
292 public String call(){
293 String result = availableFontFamily(FontEnv.this.fontFamilyList);