/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.ui;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static java.util.Locale.ENGLISH;
/**
* @author Sergey.Malenkov
*/
public final class FontInfo {
private static final FontInfoComparator COMPARATOR = new FontInfoComparator();
private static final FontRenderContext DEFAULT_CONTEXT = new FontRenderContext(null, false, false);
private static final String[] WRONG_SUFFIX = {".plain", ".bold", ".italic", ".bolditalic"};
private static final String[] DEFAULT = {Font.DIALOG, Font.DIALOG_INPUT, Font.MONOSPACED, Font.SANS_SERIF, Font.SERIF};
private static final int DEFAULT_SIZE = 12;
private final String myName;
private final int myDefaultSize;
private final boolean myMonospaced;
private volatile Font myFont;
private FontInfo(String name, Font font, boolean monospaced) {
myName = name;
myFont = font;
myDefaultSize = font.getSize();
myMonospaced = monospaced;
}
/**
* @return {@code true} if font is monospaced, {@code false} otherwise
*/
public boolean isMonospaced() {
return myMonospaced;
}
/**
* @return a font with the default size
*/
public Font getFont() {
return getFont(myDefaultSize);
}
/**
* @param size required font size
* @return a font with the specified size
*/
public Font getFont(int size) {
Font font = myFont;
if (size != font.getSize()) {
font = font.deriveFont((float)size);
myFont = font;
}
return font;
}
/**
* @return a font name
*/
@Override
public String toString() {
return myName != null ? myName : myFont.getFontName(ENGLISH);
}
/**
* @param name the font name to validate
* @return an information about the specified font name,
* or {@code null} a font cannot render english letters
* @see GraphicsEnvironment#isHeadless
*/
public static FontInfo get(String name) {
return name == null || GraphicsEnvironment.isHeadless() ? null : find(LazyListByName.LIST, name);
}
/**
* @param font the font to validate
* @return an information about the specified font name,
* or {@code null} a font cannot render english letters
* @see GraphicsEnvironment#isHeadless
*/
public static FontInfo get(Font font) {
return font == null || GraphicsEnvironment.isHeadless() ? null : find(LazyListByFont.LIST, font.getFontName(ENGLISH));
}
/**
* @param withAllStyles {@code true} - all fonts, {@code false} - all plain fonts
* @return a shared list of fonts according to the specified parameter
* @see GraphicsEnvironment#isHeadless
*/
public static List<FontInfo> getAll(boolean withAllStyles) {
return GraphicsEnvironment.isHeadless()
? Collections.<FontInfo>emptyList()
: withAllStyles
? LazyListByFont.LIST
: LazyListByName.LIST;
}
private static FontInfo find(List<FontInfo> list, String name) {
for (FontInfo info : list) {
if (info.toString().equalsIgnoreCase(name)) {
return info;
}
}
return null;
}
private static FontInfo byName(String name) {
return isWrongSuffix(name) ? null : create(name, null);
}
private static List<FontInfo> byName() {
String[] names = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(ENGLISH);
List<FontInfo> list = new ArrayList<FontInfo>(names.length);
for (String name : names) {
FontInfo info = byName(name);
if (info != null) list.add(info);
}
for (String name : DEFAULT) {
if (find(list, name) == null) {
FontInfo info = byName(name);
if (info != null) list.add(info);
}
}
Collections.sort(list, COMPARATOR);
return Collections.unmodifiableList(list);
}
private static FontInfo byFont(Font font) {
return isWrongSuffix(font.getFontName(ENGLISH)) ? null : create(null, font);
}
private static List<FontInfo> byFont() {
Font[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
List<FontInfo> list = new ArrayList<FontInfo>(fonts.length);
for (Font font : fonts) {
FontInfo info = byFont(font);
if (info != null) list.add(info);
}
for (String name : DEFAULT) {
FontInfo info = find(list, name);
if (info != null) list.remove(info);
}
Collections.sort(list, COMPARATOR);
return Collections.unmodifiableList(list);
}
private static FontInfo create(String name, Font font) {
boolean plainOnly = name == null;
try {
if (font == null) {
font = new Font(name, Font.PLAIN, DEFAULT_SIZE);
// Java uses Dialog family for nonexistent fonts
if (!Font.DIALOG.equals(name) && Font.DIALOG.equals(font.getFamily(ENGLISH))) {
throw new IllegalArgumentException("not supported " + font);
}
}
else if (DEFAULT_SIZE != font.getSize()) {
font = font.deriveFont((float)DEFAULT_SIZE);
name = font.getFontName(ENGLISH);
}
int width = getFontWidth(font, Font.PLAIN);
if (!plainOnly) {
if (width != getFontWidth(font, Font.BOLD)) width = 0;
if (width != getFontWidth(font, Font.ITALIC)) width = 0;
if (width != getFontWidth(font, Font.BOLD | Font.ITALIC)) width = 0;
}
return new FontInfo(name, font, width > 0);
}
catch (Throwable ignored) {
return null; // skip font that cannot be processed
}
}
private static boolean isWrongSuffix(String name) {
for (String suffix : WRONG_SUFFIX) {
if (name.endsWith(suffix)) {
return true;
}
}
return false;
}
private static int getFontWidth(Font font, int mask) {
if (mask != Font.PLAIN) {
//noinspection MagicConstant
font = font.deriveFont(mask ^ font.getStyle());
}
int width = getCharWidth(font, ' ');
return width == getCharWidth(font, 'l') && width == getCharWidth(font, 'W') ? width : 0;
}
private static int getCharWidth(Font font, char ch) {
if (font.canDisplay(ch)) {
Rectangle bounds = font.getStringBounds(new char[]{ch}, 0, 1, DEFAULT_CONTEXT).getBounds();
if (!bounds.isEmpty()) return bounds.width;
}
throw new IllegalArgumentException(Integer.toHexString(ch) + " cannot be rendered by " + font);
}
private static final class LazyListByName {
private static final List<FontInfo> LIST = byName();
}
private static final class LazyListByFont {
private static final List<FontInfo> LIST = byFont();
}
private static final class FontInfoComparator implements Comparator<FontInfo> {
@Override
public int compare(FontInfo one, FontInfo two) {
if (one.isMonospaced() && !two.isMonospaced()) return -1;
if (!one.isMonospaced() && two.isMonospaced()) return 1;
return one.toString().compareTo(two.toString());
}
}
}