/**************************************************************************
OmegaT - Computer Assisted Translation (CAT) tool
with fuzzy matching, translation memory, keyword search,
glossaries, and translation leveraging into updated projects.
Copyright (C) 2015 Aaron Madlon-Kay
Home page: http://www.omegat.org/
Support center: http://groups.yahoo.com/group/OmegaT/
This file is part of OmegaT.
OmegaT is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmegaT is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/
package org.omegat.util.gui;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import java.util.stream.Stream;
public class FontFallbackManager {
/**
* List of fonts that are not supported. All font names
* must be suffixed with ";" (even the last one).
* <p>
* Apple Color Emoji is blacklisted because it requires Apple-specific
* rendering techniques that Swing does not support.
* <p>
* Known working emoji fonts:
* <ul><li>Segoe UI Emoji (bundled with Windows 7 and later)
* <li><a href="https://github.com/googlei18n/noto-emoji">Noto Emoji</a>
* </ul>
*/
private static final Set<String> FONT_BLACKLIST = Collections.singleton("Apple Color Emoji");
private static final Font FONT_UNAVAILABLE = new Font("", 0, 0);
private static final Logger LOGGER = Logger.getLogger(FontFallbackManager.class.getName());
private static final Font[] recentFonts = new Font[8];
private static int lastFontIndex = 0;
private static final Map<Integer, Font> cache = new ConcurrentHashMap<>();
public static Font getCapableFont(int cp) {
// Skip variation selectors
if (cp >= '\uFE00' && cp <= '\uFE0F') {
return null;
}
if (cache.isEmpty()) {
// Prevent concurrent accesses just the first time, so that we don't
// get multiple expensive full-searches of the font list.
synchronized (cache) {
return getCapableFontInternal(cp);
}
} else {
return getCapableFontInternal(cp);
}
}
private static Font getCapableFontInternal(int cp) {
// Iterate backwards through recent fonts.
// Presumably, most fallback chars in a given document will be the same
// language/script/etc. so they are likely to be included in the same font.
for (int testIndex, i = 0; i < recentFonts.length; i++) {
testIndex = (lastFontIndex - i + recentFonts.length) % recentFonts.length;
Font font = recentFonts[testIndex];
if (font != null && font.canDisplay(cp)) {
lastFontIndex = testIndex;
cache.put(cp, font);
return font;
}
}
// Try cache in case we've seen this codepoint before.
Font cachedFont = cache.get(cp);
if (cachedFont == FONT_UNAVAILABLE) {
return null;
}
if (cachedFont != null) {
addRecentFont(cachedFont);
return cachedFont;
}
// All we can do now is do a brute-force full search of available fonts.
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
Font[] allFonts = ge.getAllFonts();
LOGGER.fine(() -> String.format("Searching %d fonts for one supporting U+%h %s", allFonts.length, cp,
String.valueOf(Character.toChars(cp))));
long start = System.currentTimeMillis();
Optional<Font> font = Stream.of(allFonts).parallel().filter(f -> {
return f.canDisplay(cp) && !FONT_BLACKLIST.contains(f.getFamily());
}).findFirst();
cache.put(cp, font.orElse(FONT_UNAVAILABLE));
font.ifPresent(FontFallbackManager::addRecentFont);
LOGGER.fine(() -> font.isPresent()
? String.format("Search found %s in %d ms", font.get().getFamily(),
System.currentTimeMillis() - start)
: String.format("Search failed to find a font; time: %d ms",
System.currentTimeMillis() - start));
return font.orElse(null);
}
private static void addRecentFont(Font font) {
lastFontIndex = (lastFontIndex + 1) % recentFonts.length;
recentFonts[lastFontIndex] = font;
}
}