1 /* 2 * Copyright (c) 2003, 2020, 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.logInfo( 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.logInfo( 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.logInfo( 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.logInfo(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.logWarning("Rejecting adding " + font + 253 " of lower rank " + font.getRank() + 254 " to family " + this + 255 " of rank " + familyRank); 256 } 257 return; 258 } 259 260 switch (style) { 261 262 case Font.PLAIN: 263 if (preferredWidth(font) && closerWeight(plain, font, style)) { 264 plain = font; 265 } 266 break; 267 268 case Font.BOLD: 269 if (preferredWidth(font) && closerWeight(bold, font, style)) { 270 bold = font; 271 } 272 break; 273 274 case Font.ITALIC: 275 if (preferredWidth(font) && closerWeight(italic, font, style)) { 276 italic = font; 277 } 278 break; 279 280 case Font.BOLD|Font.ITALIC: 281 if (preferredWidth(font) && closerWeight(bolditalic, font, style)) { 282 bolditalic = font; 283 } 284 break; 285 286 default: 287 break; 288 } 289 } 290 291 public Font2D getFontWithExactStyleMatch(int style) { 292 293 switch (style) { 294 295 case Font.PLAIN: 296 return plain; 297 298 case Font.BOLD: 299 return bold; 300 301 case Font.ITALIC: 302 return italic; 303 304 case Font.BOLD|Font.ITALIC: 305 return bolditalic; 306 307 default: 308 return null; 309 } 310 } 311 312 /* REMIND: if the callers of this method are operating in an 313 * environment in which not all fonts are registered, the returned 314 * font may be a algorithmically styled one, where in fact if loadfonts 315 * were executed, a styled font may be located. Our present "solution" 316 * to this is to register all fonts in a directory and assume that this 317 * registered all the styles of a font, since they would all be in the 318 * same location. 319 */ 320 public Font2D getFont(int style) { 321 322 switch (style) { 323 324 case Font.PLAIN: 325 return plain; 326 327 case Font.BOLD: 328 if (bold != null) { 329 return bold; 330 } else if (plain != null && plain.canDoStyle(style)) { 331 return plain; 332 } else { 333 return null; 334 } 335 336 case Font.ITALIC: 337 if (italic != null) { 338 return italic; 339 } else if (plain != null && plain.canDoStyle(style)) { 340 return plain; 341 } else { 342 return null; 343 } 344 345 case Font.BOLD|Font.ITALIC: 346 if (bolditalic != null) { 347 return bolditalic; 348 } else if (bold != null && bold.canDoStyle(style)) { 349 return bold; 350 } else if (italic != null && italic.canDoStyle(style)) { 351 return italic; 352 } else if (plain != null && plain.canDoStyle(style)) { 353 return plain; 354 } else { 355 return null; 356 } 357 default: 358 return null; 359 } 360 } 361 362 /* Only to be called if getFont(style) returns null 363 * This method will only return null if the family is completely empty! 364 * Note that it assumes the font of the style you need isn't in the 365 * family. The logic here is that if we must substitute something 366 * it might as well be from the same family. 367 */ 368 Font2D getClosestStyle(int style) { 369 370 switch (style) { 371 /* if you ask for a plain font try to return a non-italic one, 372 * then a italic one, finally a bold italic one */ 373 case Font.PLAIN: 374 if (bold != null) { 375 return bold; 376 } else if (italic != null) { 377 return italic; 378 } else { 379 return bolditalic; 380 } 381 382 /* if you ask for a bold font try to return a non-italic one, 383 * then a bold italic one, finally an italic one */ 384 case Font.BOLD: 385 if (plain != null) { 386 return plain; 387 } else if (bolditalic != null) { 388 return bolditalic; 389 } else { 390 return italic; 391 } 392 393 /* if you ask for a italic font try to return a bold italic one, 394 * then a plain one, finally an bold one */ 395 case Font.ITALIC: 396 if (bolditalic != null) { 397 return bolditalic; 398 } else if (plain != null) { 399 return plain; 400 } else { 401 return bold; 402 } 403 404 case Font.BOLD|Font.ITALIC: 405 if (italic != null) { 406 return italic; 407 } else if (bold != null) { 408 return bold; 409 } else { 410 return plain; 411 } 412 } 413 return null; 414 } 415 416 /* Font may have localized names. Store these in a separate map, so 417 * that only clients who use these names need be affected. 418 */ 419 static synchronized void addLocaleNames(FontFamily family, String[] names){ 420 if (allLocaleNames == null) { 421 allLocaleNames = new HashMap<String, FontFamily>(); 422 } 423 for (int i=0; i<names.length; i++) { 424 allLocaleNames.put(names[i].toLowerCase(), family); 425 } 426 } 427 428 public static synchronized FontFamily getLocaleFamily(String name) { 429 if (allLocaleNames == null) { 430 return null; 431 } 432 return allLocaleNames.get(name.toLowerCase()); 433 } 434 435 public static FontFamily[] getAllFontFamilies() { 436 Collection<FontFamily> families = familyNameMap.values(); 437 return families.toArray(new FontFamily[0]); 438 } 439 440 public String toString() { 441 return 442 "Font family: " + familyName + 443 " plain="+plain+ 444 " bold=" + bold + 445 " italic=" + italic + 446 " bolditalic=" + bolditalic; 447 448 } 449 450 }