Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,21 @@
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Originally created by azakhary on 4/24/2015
* see https://github.com/UnderwaterApps/overlap2d/blob/master/overlap2d/src/com/uwsoft/editor/proxy/FontManager.java
* see <a href="https://github.com/UnderwaterApps/overlap2d/blob/master/overlap2d/src/com/uwsoft/editor/proxy/FontManager.java">...</a>
* (Apache 2.0 license)
* Edited by Clement Grennerat
*/
public class SystemFontsMapper {

// Family, Paths

private static final String[] FONT_EXTENSIONS = {"ttf", "otf", "ttc"};
private static final double MILLIS_PER_SECOND = 1000d;
private static final int NOTIFICATION_TIMEOUT_SECONDS = 30;

// Family, Paths
private final HashMap<String, FontPaths> systemFontMap = new HashMap<>();

public static String[] getSystemFontNames(){
Expand All @@ -46,71 +51,41 @@ public static String[] getSystemFontNames(){
return new String[0];
}
}

private String[] getSystemFontsDirs(){
String[] result;

public List<File> getAllSystemFontFiles(){
if(PlatformUtils.isWindows()){
result = new String[2];
result[0] = System.getenv("WINDIR") + File.separator + "Fonts";
result[1] = System.getenv("LOCALAPPDATA") + File.separator + "Microsoft\\Windows\\Fonts";
return result;

}else if(PlatformUtils.isMac()){
result = new String[3];
result[0] = System.getProperty("user.home") + File.separator + "Library/Fonts";
result[1] = "/Library/Fonts";
result[2] = "/System/Library/Fonts";
return result;

}else if(PlatformUtils.isLinux()){
String[] pathsToCheck = {
System.getProperty("user.home") + File.separator + ".fonts",
"/usr/share/fonts/truetype",
"/usr/share/fonts/TTF",
"/usr/local/share/fonts/"
};
ArrayList<String> resultList = new ArrayList<>();

for(int i = pathsToCheck.length - 1; i >= 0; i--){
String path = pathsToCheck[i];
File tmp = new File(path);
if(tmp.exists() && tmp.isDirectory() && tmp.canRead()){
resultList.add(path);
}
}

if(resultList.isEmpty()){
MainWindow.showNotification(AlertIconType.WARNING, "Error: unable to load system fonts, no directory is readable", 30);
result = new String[0];
}else{
result = new String[resultList.size()];
result = resultList.toArray(result);
}
return result;
return scanDirectoriesForFonts(List.of(
System.getenv("WINDIR") + File.separator + "Fonts",
System.getenv("LOCALAPPDATA") + File.separator + "Microsoft\\Windows\\Fonts"
));
}
return null;
}
public List<File> getAllSystemFontFiles(){
// only retrieving ttf files
String[] extensions = {"ttf", "otf", "ttc"};
String[] paths = getSystemFontsDirs();
return Arrays.stream(paths)
.map(File::new)
.filter(File::exists)
.flatMap(fontDirectory -> FilesUtils.listFiles(fontDirectory, extensions, true)
.stream())
.collect(Collectors.toCollection(ArrayList::new));
if(PlatformUtils.isMac()){
return scanDirectoriesForFonts(List.of(
System.getProperty("user.home") + File.separator + "Library/Fonts",
"/Library/Fonts",
"/System/Library/Fonts"
));
}
if(PlatformUtils.isLinux()){
// Use fc-list to get font files directly (works on NixOS and other non-standard setups)
List<File> fcListFiles = getLinuxFontFilesViaFcList();
if(!fcListFiles.isEmpty()) return fcListFiles;

// Fallback to directory scanning if fc-list fails
return scanDirectoriesForFonts(getLinuxFontDirs());
}
return List.of();
}

public void loadFontsFromSystemFiles(){
String[] systemFonts = getSystemFontNames();
Log.i("Indexing system fonts... (" + systemFonts.length + " fonts)");

new Thread(() -> {
long time = System.currentTimeMillis();
int pdfBoxErrors = 0;


systemFontMap.clear();
int pdfBoxErrors = 0;
for(File file : getAllSystemFontFiles()){
try{
Font[] fonts = Font.loadFonts(new FileInputStream(file.getAbsolutePath()), -1);
Expand All @@ -122,30 +97,101 @@ public void loadFontsFromSystemFiles(){
}
}catch(IOException e){Log.eNotified(e);}
}
double ping = (System.currentTimeMillis() - time) / 1000d;
Log.i("Loaded " + systemFontMap.size() + "/" + systemFonts.length + " fonts in " + ping + "s (" + pdfBoxErrors + " unable to load due to PDFBox restrictions, usually missing tables)");

double ping = (System.currentTimeMillis() - time) / MILLIS_PER_SECOND;
Log.i("Loaded " + systemFontMap.size() + '/' + systemFonts.length + " fonts in " + ping + "s (" + pdfBoxErrors + " unable to load due to PDFBox restrictions, usually missing tables)");

Platform.runLater(FontUtils::fontsUpdated);
}, "System fonts loader").start();
}

public void loadFontsFromCache(List<FontPaths> fontPathss){
for(FontPaths fontPaths : fontPathss){
systemFontMap.put(fontPaths.getName(), fontPaths);
}
Platform.runLater(FontUtils::fontsUpdated);
}

public FontPaths getFontPathsFromName(String fontFamily){
return systemFontMap.get(fontFamily);
}

public HashMap<String, FontPaths> getFonts(){
return systemFontMap;
}

public boolean hasFont(String fontFamily){
return systemFontMap.containsKey(fontFamily);
}

private List<File> scanDirectoriesForFonts(List<String> directories){
return directories.stream()
.map(File::new)
.filter(File::exists)
.flatMap(fontDirectory -> FilesUtils.listFiles(fontDirectory, FONT_EXTENSIONS, true).stream())
.collect(Collectors.toCollection(ArrayList::new));
}

private List<File> getLinuxFontFilesViaFcList(){
try{
ProcessBuilder pb = new ProcessBuilder("fc-list", "--format=%{file}\n");
pb.redirectErrorStream(true);
Process process = pb.start();
String output = new String(process.getInputStream().readAllBytes(), java.nio.charset.StandardCharsets.UTF_8);
process.waitFor();

return Arrays.stream(output.split("\n"))
.filter(line -> !line.isEmpty())
.map(File::new)
.filter(file -> file.exists() && file.canRead())
.toList();
}catch(IOException | InterruptedException e){
Log.eNotified(e);
return List.of();
}
}

private List<String> getLinuxFontDirs(){
String userHome = System.getProperty("user.home");
Stream<String> standardPaths = Stream.of(
userHome + "/.local/share/fonts",
userHome + "/.fonts",
"/usr/share/fonts/truetype",
"/usr/share/fonts/TTF",
"/usr/local/share/fonts/"
);

// Add font directories from XDG_DATA_DIRS (for NixOS and other non-standard setups)
String xdgDataDirs = System.getenv("XDG_DATA_DIRS");
Stream<String> xdgPaths = (xdgDataDirs != null && !xdgDataDirs.isEmpty())
? Arrays.stream(xdgDataDirs.split(":"))
.filter(dir -> !dir.isEmpty())
.map(dir -> dir + "/fonts")
: Stream.empty();

List<String> result = Stream.concat(xdgPaths, standardPaths)
.map(File::new)
.filter(file -> file.exists() && file.isDirectory() && file.canRead())
.map(File::getAbsolutePath)
.toList();

if(result.isEmpty()){
Platform.runLater(() ->
MainWindow.showNotification(AlertIconType.WARNING,
"Error: unable to load system fonts, no directory is readable", NOTIFICATION_TIMEOUT_SECONDS));
}
return result;
}

private boolean addFontToMap(Font font, String path){

// Check if PDFBox is able to load the font, otherwise, cancel the add.
try(PDDocument doc = new PDDocument();
FileInputStream fis = new FileInputStream(path)){
PDType0Font.load(doc, fis);
}catch(IOException e){
return false;
}

FontPaths paths;
if(systemFontMap.containsKey(font.getFamily())){
paths = systemFontMap.get(font.getFamily());
Expand All @@ -157,14 +203,4 @@ private boolean addFontToMap(Font font, String path){

return true;
}

public FontPaths getFontPathsFromName(String fontFamily){
return systemFontMap.get(fontFamily);
}
public HashMap<String, FontPaths> getFonts(){
return systemFontMap;
}
public boolean hasFont(String fontFamily){
return systemFontMap.containsKey(fontFamily);
}
}
}