1 /* 2 * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.font; 27 28 import java.io.File; 29 import java.awt.Font; 30 import java.io.IOException; 31 import java.util.Collection; 32 import java.util.HashMap; 33 import java.util.concurrent.ConcurrentHashMap; 34 import java.util.Locale; 35 36 public class FontFamily { 37 38 private static ConcurrentHashMap<String, FontFamily> 39 familyNameMap = new ConcurrentHashMap<String, FontFamily>(); 40 private static HashMap<String, FontFamily> allLocaleNames; 41 42 protected String familyName; 43 protected Font2D plain; 44 protected Font2D bold; 45 protected Font2D italic; 46 protected Font2D bolditalic; 47 protected boolean logicalFont = false; 48 protected int familyRank; 49 50 public static FontFamily getFamily(String name) { 51 return familyNameMap.get(name.toLowerCase(Locale.ENGLISH)); 52 } 53 54 public static String[] getAllFamilyNames() { 55 return null; 56 } 57 58 /* Only for use by FontManager.deRegisterBadFont(..). 59 * If this was the only font in the family, the family is removed 60 * from the map 61 */ 62 static void remove(Font2D font2D) { 63 64 String name = font2D.getFamilyName(Locale.ENGLISH); 65 FontFamily family = getFamily(name); 66 if (family == null) { 67 return; 68 } 69 if (family.plain == font2D) { 70 family.plain = null; 71 } 72 if (family.bold == font2D) { 73 family.bold = null; 74 } 75 if (family.italic == font2D) { 76 family.italic = null; 77 } 78 if (family.bolditalic == font2D) { 79 family.bolditalic = null; 80 } 81 if (family.plain == null && family.bold == null && 82 family.italic == null && family.bolditalic == null) { 83 familyNameMap.remove(name); 84 } 85 } 86 87 public FontFamily(String name, boolean isLogFont, int rank) { 88 logicalFont = isLogFont; 89 familyName = name; 90 familyRank = rank; 91 familyNameMap.put(name.toLowerCase(Locale.ENGLISH), this); 92 } 93 94 /* Create a family for created fonts which aren't listed in the 95 * main map. 96 */ 97 FontFamily(String name) { 98 logicalFont = false; 99 familyName = name; 100 familyRank = Font2D.DEFAULT_RANK; 101 } 102 103 public String getFamilyName() { 104 return familyName; 105 } 106 107 public int getRank() { 108 return familyRank; 109 } 110 111 private boolean isFromSameSource(Font2D font) { 112 if (!(font instanceof FileFont)) { 113 return false; 114 } 115 116 FileFont existingFont = null; 117 if (plain instanceof FileFont) { 118 existingFont = (FileFont)plain; 119 } else if (bold instanceof FileFont) { 120 existingFont = (FileFont)bold; 121 } else if (italic instanceof FileFont) { 122 existingFont = (FileFont)italic; 123 } else if (bolditalic instanceof FileFont) { 124 existingFont = (FileFont)bolditalic; 125 } 126 // A family isn't created until there's a font. 127 // So if we didn't find a file font it means this 128 // isn't a file-based family. 129 if (existingFont == null) { 130 return false; 131 } 132 File existDir = (new File(existingFont.platName)).getParentFile(); 133 134 FileFont newFont = (FileFont)font; 135 File newDir = (new File(newFont.platName)).getParentFile(); 136 if (existDir != null) { 137 try { 138 existDir = existDir.getCanonicalFile(); 139 } catch (IOException ignored) {} 140 } 141 if (newDir != null) { 142 try { 143 newDir = newDir.getCanonicalFile(); 144 } catch (IOException ignored) {} 145 } 146 return java.util.Objects.equals(newDir, existDir); 147 } 148 149 /* 150 * We want a family to be of the same width and prefer medium/normal width. 151 * Once we find a particular width we accept more of the same width 152 * until we find one closer to normal when we 'evict' all existing fonts. 153 * So once we see a 'normal' width font we evict all members that are not 154 * normal width and then accept only new ones that are normal width. 155 * 156 * Once a font passes the width test we subject it to the weight test. 157 * For Plain we target the weight the closest that is <= NORMAL (400) 158 * For Bold we target the weight that is closest to BOLD (700). 159 * 160 * In the future, rather than discarding these fonts, we should 161 * extend the family to include these so lookups on these properties 162 * can locate them, as presently they will only be located by full name 163 * based lookup. 164 */ 165 166 private int familyWidth = 0; 167 private boolean preferredWidth(Font2D font) { 168 169 int newWidth = font.getWidth(); 170 171 if (familyWidth == 0) { 172 familyWidth = newWidth; 173 return true; 174 } 175 176 if (newWidth == familyWidth) { 177 return true; 178 } 179 180 if (Math.abs(Font2D.FWIDTH_NORMAL - newWidth) < 181 Math.abs(Font2D.FWIDTH_NORMAL - familyWidth)) 182 { 183 if (FontUtilities.debugFonts()) { 184 FontUtilities.getLogger().info( 185 "Found more preferred width. New width = " + newWidth + 186 " Old width = " + familyWidth + " in font " + font + 187 " nulling out fonts plain: " + plain + " bold: " + bold + 188 " italic: " + italic + " bolditalic: " + bolditalic); 189 } 190 familyWidth = newWidth; 191 plain = bold = italic = bolditalic = null; 192 return true; 193 } else if (FontUtilities.debugFonts()) { 194 FontUtilities.getLogger().info( 195 "Family rejecting font " + font + 196 " of less preferred width " + newWidth); 197 } 198 return false; 199 } 200 201 private boolean closerWeight(Font2D currFont, Font2D font, int style) { 202 if (familyWidth != font.getWidth()) { 203 return false; 204 } 205 206 if (currFont == null) { 207 return true; 208 } 209 210 if (FontUtilities.debugFonts()) { 211 FontUtilities.getLogger().info( 212 "New weight for style " + style + ". Curr.font=" + currFont + 213 " New font="+font+" Curr.weight="+ + currFont.getWeight()+ 214 " New weight="+font.getWeight()); 215 } 216 217 int newWeight = font.getWeight(); 218 switch (style) { 219 case Font.PLAIN: 220 case Font.ITALIC: 221 return (newWeight <= Font2D.FWEIGHT_NORMAL && 222 newWeight > currFont.getWeight()); 223 224 case Font.BOLD: 225 case Font.BOLD|Font.ITALIC: 226 return (Math.abs(newWeight - Font2D.FWEIGHT_BOLD) < 227 Math.abs(currFont.getWeight() - Font2D.FWEIGHT_BOLD)); 228 229 default: 230 return false; 231 } 232 } 233 234 public void setFont(Font2D font, int style) { 235 236 if (FontUtilities.isLogging()) { 237 String msg; 238 if (font instanceof CompositeFont) { 239 msg = "Request to add " + font.getFamilyName(null) + 240 " with style " + style + " to family " + familyName; 241 } else { 242 msg = "Request to add " + font + 243 " with style " + style + " to family " + this; 244 } 245 FontUtilities.getLogger().info(msg); 246 } 247 /* Allow a lower-rank font only if its a file font 248 * from the exact same source as any previous font. 249 */ 250 if ((font.getRank() > familyRank) && !isFromSameSource(font)) { 251 if (FontUtilities.isLogging()) { 252 FontUtilities.getLogger() 253 .warning("Rejecting adding " + font + 254 " of lower rank " + font.getRank() + 255 " to family " + this + 256 " of rank " + familyRank); 257 } 258 return; 259 } 260 261 switch (style) { 262 263 case Font.PLAIN: 264 if (preferredWidth(font) && closerWeight(plain, font, style)) { 265 plain = font; 266 } 267 break; 268 269 case Font.BOLD: 270 if (preferredWidth(font) && closerWeight(bold, font, style)) { 271 bold = font; 272 } 273 break; 274 275 case Font.ITALIC: 276 if (preferredWidth(font) && closerWeight(italic, font, style)) { 277 italic = font; 278 } 279 break; 280 281 case Font.BOLD|Font.ITALIC: 282 if (preferredWidth(font) && closerWeight(bolditalic, font, style)) { 283 bolditalic = font; 284 } 285 break; 286 287 default: 288 break; 289 } 290 } 291 292 public Font2D getFontWithExactStyleMatch(int style) { 293 294 switch (style) { 295 296 case Font.PLAIN: 297 return plain; 298 299 case Font.BOLD: 300 return bold; 301 302 case Font.ITALIC: 303 return italic; 304 305 case Font.BOLD|Font.ITALIC: 306 return bolditalic; 307 308 default: 309 return null; 310 } 311 } 312 313 /* REMIND: if the callers of this method are operating in an 314 * environment in which not all fonts are registered, the returned 315 * font may be a algorithmically styled one, where in fact if loadfonts 316 * were executed, a styled font may be located. Our present "solution" 317 * to this is to register all fonts in a directory and assume that this 318 * registered all the styles of a font, since they would all be in the 319 * same location. 320 */ 321 public Font2D getFont(int style) { 322 323 switch (style) { 324 325 case Font.PLAIN: 326 return plain; 327 328 case Font.BOLD: 329 if (bold != null) { 330 return bold; 331 } else if (plain != null && plain.canDoStyle(style)) { 332 return plain; 333 } else { 334 return null; 335 } 336 337 case Font.ITALIC: 338 if (italic != null) { 339 return italic; 340 } else if (plain != null && plain.canDoStyle(style)) { 341 return plain; 342 } else { 343 return null; 344 } 345 346 case Font.BOLD|Font.ITALIC: 347 if (bolditalic != null) { 348 return bolditalic; 349 } else if (bold != null && bold.canDoStyle(style)) { 350 return bold; 351 } else if (italic != null && italic.canDoStyle(style)) { 352 return italic; 353 } else if (plain != null && plain.canDoStyle(style)) { 354 return plain; 355 } else { 356 return null; 357 } 358 default: 359 return null; 360 } 361 } 362 363 /* Only to be called if getFont(style) returns null 364 * This method will only return null if the family is completely empty! 365 * Note that it assumes the font of the style you need isn't in the 366 * family. The logic here is that if we must substitute something 367 * it might as well be from the same family. 368 */ 369 Font2D getClosestStyle(int style) { 370 371 switch (style) { 372 /* if you ask for a plain font try to return a non-italic one, 373 * then a italic one, finally a bold italic one */ 374 case Font.PLAIN: 375 if (bold != null) { 376 return bold; 377 } else if (italic != null) { 378 return italic; 379 } else { 380 return bolditalic; 381 } 382 383 /* if you ask for a bold font try to return a non-italic one, 384 * then a bold italic one, finally an italic one */ 385 case Font.BOLD: 386 if (plain != null) { 387 return plain; 388 } else if (bolditalic != null) { 389 return bolditalic; 390 } else { 391 return italic; 392 } 393 394 /* if you ask for a italic font try to return a bold italic one, 395 * then a plain one, finally an bold one */ 396 case Font.ITALIC: 397 if (bolditalic != null) { 398 return bolditalic; 399 } else if (plain != null) { 400 return plain; 401 } else { 402 return bold; 403 } 404 405 case Font.BOLD|Font.ITALIC: 406 if (italic != null) { 407 return italic; 408 } else if (bold != null) { 409 return bold; 410 } else { 411 return plain; 412 } 413 } 414 return null; 415 } 416 417 /* Font may have localized names. Store these in a separate map, so 418 * that only clients who use these names need be affected. 419 */ 420 static synchronized void addLocaleNames(FontFamily family, String[] names){ 421 if (allLocaleNames == null) { 422 allLocaleNames = new HashMap<String, FontFamily>(); 423 } 424 for (int i=0; i<names.length; i++) { 425 allLocaleNames.put(names[i].toLowerCase(), family); 426 } 427 } 428 429 public static synchronized FontFamily getLocaleFamily(String name) { 430 if (allLocaleNames == null) { 431 return null; 432 } 433 return allLocaleNames.get(name.toLowerCase()); 434 } 435 436 public static FontFamily[] getAllFontFamilies() { 437 Collection<FontFamily> families = familyNameMap.values(); 438 return families.toArray(new FontFamily[0]); 439 } 440 441 public String toString() { 442 return 443 "Font family: " + familyName + 444 " plain="+plain+ 445 " bold=" + bold + 446 " italic=" + italic + 447 " bolditalic=" + bolditalic; 448 449 } 450 451 }