1
2
3
4
5
6
7 package com.octo.captcha.component.image.fontgenerator;
8
9 import java.awt.Font;
10 import java.awt.GraphicsEnvironment;
11 import java.security.SecureRandom;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.Random;
17
18 /***
19 * <p>Description: Random font generator that return one of the available system's (or optionay specified) fonts, using a min and max
20 * font size. This list is formerly cleaned of OCR readable font and symbol font</p>
21 *
22 * @author <a href="mailto:mag@jcaptcha.net">Marc-Antoine Garrigue</a>
23 * @version 1.0
24 */
25 public class RandomFontGenerator extends AbstractFontGenerator {
26
27 /***
28 * These are the valid font styles.
29 */
30 private int[] STYLES = {Font.PLAIN, Font.ITALIC, Font.BOLD, Font.ITALIC | Font.BOLD};
31
32
33
34 /***
35 * Any font that this class uses must be able to generate all of the characters in this list.
36 */
37 private String requiredCharacters = "abcdefghijklmnopqrstuvwxyz0123456789";
38
39 /***
40 * Prefixes of font names that are avoided by default. The default values list fonts that are totally fine in terms of
41 * representing characters, of course, but they're too commonly available in OCR programs.
42 */
43 public static String[] defaultBadFontNamePrefixes = {
44 "Courier",
45 "Times Roman",
46 };
47
48 /***
49 * Prefixes of font names that should be avoided. The default values list fonts that are totally fine in terms of
50 * representing characters, of course, but they're too commonly available in OCR programs.
51 */
52 private String[] badFontNamePrefixes = defaultBadFontNamePrefixes;
53
54 private static final int GENERATED_FONTS_ARRAY_SIZE = 3000;
55
56 private boolean mixStyles = true;
57
58 private Font[] generatedFonts = null;
59
60 protected Random myRandom = new SecureRandom();
61
62 public RandomFontGenerator(Integer minFontSize, Integer maxFontSize) {
63 super(minFontSize, maxFontSize);
64 initializeFonts(GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts());
65 }
66
67 public RandomFontGenerator(Integer minFontSize, Integer maxFontSize, Font[] fontsList) {
68 super(minFontSize, maxFontSize);
69 if (fontsList == null || fontsList.length < 1) {
70 throw new IllegalArgumentException("fonts list cannot be null or empty");
71 }
72 initializeFonts(fontsList);
73 }
74
75 public RandomFontGenerator(Integer minFontSize, Integer maxFontSize, Font[] fontsList, boolean mixStyles) {
76 super(minFontSize, maxFontSize);
77 if (fontsList == null || fontsList.length < 1) {
78 throw new IllegalArgumentException("fonts list cannot be null or empty");
79 }
80 this.mixStyles = mixStyles;
81 initializeFonts(fontsList);
82 }
83
84 public RandomFontGenerator(Integer minFontSize, Integer maxFontSize, String[] badFontNamePrefixes) {
85 super(minFontSize, maxFontSize);
86 this.badFontNamePrefixes = badFontNamePrefixes;
87 initializeFonts(GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts());
88 }
89
90
91
92
93 private void initializeFonts(Font[] fontList) {
94 List fonts = cleanFontList(fontList);
95 checkInitializedFontListSize(fonts);
96 generatedFonts = generateCustomStyleFontArray(fonts);
97 }
98
99 private void checkInitializedFontListSize(List fontList) {
100 if (fontList.size() < 1) {
101 throw new IllegalArgumentException("fonts list cannot be null or empty, some of your font are removed from the list by this class, Courrier and TimesRoman");
102 }
103 }
104
105 /***
106 * Method from imageFromWord method to apply font to String. Implementations must take into account the minFontSize
107 * and the MaxFontSize.
108 *
109 * @return a Font
110 */
111 public Font getFont() {
112 return generatedFonts[Math.abs(myRandom.nextInt(GENERATED_FONTS_ARRAY_SIZE))];
113 }
114
115 /***
116 * @return a array of generated Fonts
117 */
118 private Font[] generateCustomStyleFontArray(List fontList) {
119 Font[] generatedFonts = new Font[GENERATED_FONTS_ARRAY_SIZE];
120 for (int i = 0; i < GENERATED_FONTS_ARRAY_SIZE; i++) {
121 Font font = (Font) fontList.get(myRandom.nextInt(fontList.size()));
122 Font styled = applyStyle(font);
123 generatedFonts[i] = applyCustomDeformationOnGeneratedFont(styled);
124 }
125 return generatedFonts;
126 }
127
128 protected Font applyStyle(Font font) {
129 int fontSizeIncrement = 0;
130 if (getFontSizeDelta() > 0) {
131 fontSizeIncrement = Math.abs(myRandom.nextInt(getFontSizeDelta()));
132 }
133
134 Font styled = font.deriveFont(
135 mixStyles?
136 STYLES[myRandom.nextInt(STYLES.length)]:
137 font.getStyle(),
138 getMinFontSize() + fontSizeIncrement);
139 return styled;
140 }
141
142 private int getFontSizeDelta() {
143 return getMaxFontSize() - getMinFontSize();
144 }
145
146 /***
147 * Provides a way for children class to customize the generated font array
148 *
149 * @param font
150 * @return a customized font
151 */
152 protected Font applyCustomDeformationOnGeneratedFont(Font font) {
153 return font;
154 }
155
156
157 /***
158 * Create an array of fonts that is known to properly represent all the characters in requiredCharacters.
159 *
160 * @return array of fonts
161 * @see #requiredCharacters
162 */
163 protected List cleanFontList(Font[] uncheckFonts) {
164
165
166
167
168
169 List goodFonts = new ArrayList(uncheckFonts.length);
170
171
172 goodFonts.addAll(Arrays.asList(uncheckFonts));
173
174
175 for (Iterator iter = goodFonts.iterator(); iter.hasNext();) {
176
177 Font f = (Font) iter.next();
178 if (!checkFontNamePrefix(iter, f)) {
179 checkFontCanDisplayCharacters(iter, f);
180 }
181 }
182
183 return goodFonts;
184 }
185
186 /***
187 * @param iter Font iterator
188 * @param f The current font
189 * @return true if the font has been removed
190 */
191 private boolean checkFontNamePrefix(Iterator iter, Font f) {
192
193 boolean removed = false;
194
195
196 for (int i = 0; i < badFontNamePrefixes.length; i++) {
197 String prefix = badFontNamePrefixes[i];
198
199 if (prefix != null && !"".equals(prefix)) {
200
201 if (f.getName().startsWith(prefix)) {
202 iter.remove();
203 removed = true;
204 break;
205 }
206 }
207 }
208
209 return removed;
210 }
211
212 /***
213 * @param iter Font iterator
214 * @param f The current font
215 * @return true if the font has been removed
216 */
217 private boolean checkFontCanDisplayCharacters(Iterator iter, Font f) {
218
219 boolean removed = false;
220
221
222 for (int i = 0; i < requiredCharacters.length(); i++) {
223 if (!f.canDisplay(requiredCharacters.charAt(i))) {
224 iter.remove();
225 removed = true;
226 break;
227 }
228 }
229 return removed;
230 }
231
232 /***
233 * @return a list of characters that this class must be able to represent
234 */
235 public String getRequiredCharacters() {
236 return requiredCharacters;
237 }
238
239 /***
240 * @param requiredCharacters a list of characters that this class must be able to represent
241 */
242 public void setRequiredCharacters(String requiredCharacters) {
243 this.requiredCharacters = requiredCharacters;
244
245 }
246
247 /***
248 * @return an array of font name prefixes that should be not used in generating captchas
249 */
250 public String[] getBadFontNamePrefixes() {
251 return badFontNamePrefixes;
252 }
253
254 /***
255 * @param badFontNamePrefixes an array of font name prefixes that should be not used in generating captchas
256 */
257 public void setBadFontNamePrefixes(String[] badFontNamePrefixes) {
258 this.badFontNamePrefixes = badFontNamePrefixes;
259 }
260
261 }