View Javadoc

1   /*
2    * JCaptcha, the open source java framework for captcha definition and integration
3    * Copyright (c)  2007 jcaptcha.net. All Rights Reserved.
4    * See the LICENSE.txt file distributed with this package.
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         // get a copy of the fonts
166         // NB: be careful with this first array! -- the graphics environment obligingly
167         // provides a pointer into its internal font array.
168 
169         List goodFonts = new ArrayList(uncheckFonts.length);
170         // add copy of copy of list of fonts because of asList's special class and also because
171         // of the graphics environment's internal point
172         goodFonts.addAll(Arrays.asList(uncheckFonts));
173 
174         // Iterate through all fonts, remove the bad ones
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 		// a font is also removed if it is prefixed by a known-bad name
196 		for (int i = 0; i < badFontNamePrefixes.length; i++) {
197 		    String prefix = badFontNamePrefixes[i];
198 		    // verify prefix is not null
199 		    if (prefix != null && !"".equals(prefix)) {
200 		        // verify font name start with prefix
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 		// a font is removed if it cannot display the characters we need.
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 }