diff --git a/src/main/java/fr/clementgre/pdf4teachers/utils/fonts/SystemFontsMapper.java b/src/main/java/fr/clementgre/pdf4teachers/utils/fonts/SystemFontsMapper.java index dc719b71..25136afa 100644 --- a/src/main/java/fr/clementgre/pdf4teachers/utils/fonts/SystemFontsMapper.java +++ b/src/main/java/fr/clementgre/pdf4teachers/utils/fonts/SystemFontsMapper.java @@ -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 ... * (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 systemFontMap = new HashMap<>(); public static String[] getSystemFontNames(){ @@ -46,71 +51,41 @@ public static String[] getSystemFontNames(){ return new String[0]; } } - - private String[] getSystemFontsDirs(){ - String[] result; + + public List 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 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 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 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); @@ -122,22 +97,93 @@ 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 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 getFonts(){ + return systemFontMap; + } + + public boolean hasFont(String fontFamily){ + return systemFontMap.containsKey(fontFamily); + } + private List scanDirectoriesForFonts(List 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 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 getLinuxFontDirs(){ + String userHome = System.getProperty("user.home"); + Stream 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 xdgPaths = (xdgDataDirs != null && !xdgDataDirs.isEmpty()) + ? Arrays.stream(xdgDataDirs.split(":")) + .filter(dir -> !dir.isEmpty()) + .map(dir -> dir + "/fonts") + : Stream.empty(); + + List 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)){ @@ -145,7 +191,7 @@ private boolean addFontToMap(Font font, String path){ }catch(IOException e){ return false; } - + FontPaths paths; if(systemFontMap.containsKey(font.getFamily())){ paths = systemFontMap.get(font.getFamily()); @@ -157,14 +203,4 @@ private boolean addFontToMap(Font font, String path){ return true; } - - public FontPaths getFontPathsFromName(String fontFamily){ - return systemFontMap.get(fontFamily); - } - public HashMap getFonts(){ - return systemFontMap; - } - public boolean hasFont(String fontFamily){ - return systemFontMap.containsKey(fontFamily); - } -} +} \ No newline at end of file