diff --git a/.gitignore b/.gitignore index 9f25cc2301ab500393825b61c652ac5751c5dc3b..7cbe242d3e57f29e4c908eaef1e72c996c53f81a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,8 @@ src/main/resources/mapping/liblouis # Exported SVG files *.svg + +# Native libraries +*.so +*.dylib +*.dll diff --git a/.gitmodules b/.gitmodules index c5dd365bd9aec06336502855f51c391593fd54cd..6279666aaacb70d51061cd2eaf45864903353052 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "third_party/liblouis"] path = third_party/liblouis url = https://github.com/liblouis/liblouis +[submodule "third_party/liblouis_bin"] + path = third_party/liblouis_bin + url = ../../brailleplot/liblouis_bin.git diff --git a/build.gradle b/build.gradle index 59cf0b22950c45a026679cd9f96d0232bc297d16..d56141ec73bfe993b742785d3cf4376a3c4085f3 100644 --- a/build.gradle +++ b/build.gradle @@ -68,6 +68,23 @@ clean { delete "$rootDir/src/main/resources/mapping/liblouis" } +// Copy native liblouis libraries for major platforms to the resource folder +task copyLibLouisBinary(type: Copy) { + from "$projectDir/third_party/liblouis_bin/bin/" + into "$rootDir/src/main/resources/native/liblouis" +} +processResources.dependsOn copyLibLouisBinary +// Abort if files are missing +gradle.taskGraph.afterTask { copyLibLouisBinary -> + if(copyLibLouisBinary.state.noSource){ + throw new GradleException("Could not find liblouis native libraries in \"$projectDir/third_party/liblouis_bin/bin/\". Please make sure you updated all git submodules using \"git submodule update --init --recursive\" ") + } +} +// Delete libraries on "clean" task +clean { + delete "$rootDir/src/main/resources/native/liblouis" +} + // Define the main class for the application mainClassName = 'de.tudresden.inf.mci.brailleplot.App' diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/App.java b/src/main/java/de/tudresden/inf/mci/brailleplot/App.java index cd0a7d8249f6d3d77d07e25e91fdcb248c3a3c78..a302869bea0c1dca31239b721c30e1ba7ab8a066 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/App.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/App.java @@ -153,8 +153,6 @@ public final class App { try { // Logging example mLogger.info("Application started"); - // Needed for Windows machines - System.setProperty("jna.library.path", System.getProperty("user.dir") + "/third_party"); // Parse command line parameters CommandLineParser cliParser = new CommandLineParser(); SettingsWriter settings = cliParser.parse(args); @@ -198,6 +196,7 @@ public final class App { barChart.setYAxisName("Länge in m"); // Render diagram + LiblouisBrailleTextRasterizer.initModule(); MasterRenderer renderer = new MasterRenderer(indexV4Printer, representationParameters, a4Format); RasterCanvas canvas = renderer.rasterize(barChart); // SVG exporting diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/LiblouisBrailleTextRasterizer.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/LiblouisBrailleTextRasterizer.java index 1bcbed6087ee1b293194715a4959a25c1d9855c5..e214755c5ff66fbe0a4b534b26d63eb1aa081b3b 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/LiblouisBrailleTextRasterizer.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/LiblouisBrailleTextRasterizer.java @@ -8,8 +8,11 @@ import de.tudresden.inf.mci.brailleplot.layout.InsufficientRenderingAreaExceptio import de.tudresden.inf.mci.brailleplot.layout.RasterCanvas; import de.tudresden.inf.mci.brailleplot.layout.Rectangle; import de.tudresden.inf.mci.brailleplot.printerbackend.NotSupportedFileExtensionException; +import de.tudresden.inf.mci.brailleplot.util.NativeLibraryHelper; +import de.tudresden.inf.mci.brailleplot.util.NoSuchNativeLibraryException; import org.liblouis.DisplayException; import org.liblouis.DisplayTable; +import org.liblouis.Louis; import org.liblouis.TranslationException; import org.liblouis.TranslationResult; import org.liblouis.Translator; @@ -27,6 +30,8 @@ import static java.lang.Math.ceil; public class LiblouisBrailleTextRasterizer implements Rasterizer<BrailleText> { + private static boolean mNativeLibInitialized = false; + private AbstractBrailleTableParser mParser; // Parameters for rasterizing private int x; @@ -241,4 +246,33 @@ public class LiblouisBrailleTextRasterizer implements Rasterizer<BrailleText> { mTranslator = temp; return length; } + + /** + * Initializes the Module. + * @throws LibLouisLibraryMissingException If liblouis could not be loaded from neither the jar or the default JNI include path. + */ + public static void initModule() throws LibLouisLibraryMissingException { + if (!mNativeLibInitialized) { + try { + NativeLibraryHelper.loadNativeLibrary("liblouis"); + } catch (NoSuchNativeLibraryException e) { + // Even if the library is not distributed within the jar file, it might be installed on the system. + } + try { + Louis.getVersion(); + } catch (java.lang.UnsatisfiedLinkError e) { + throw new LibLouisLibraryMissingException(e); + } + mNativeLibInitialized = true; + } + } + + /** + * Indicates, the native liblouis library was not found. + */ + public static class LibLouisLibraryMissingException extends NoSuchNativeLibraryException { + LibLouisLibraryMissingException(final Throwable cause) { + super(cause); + } + } } diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/util/NativeLibraryHelper.java b/src/main/java/de/tudresden/inf/mci/brailleplot/util/NativeLibraryHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..fb12d84492efcbfc339696d9e772692ae93417a6 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/util/NativeLibraryHelper.java @@ -0,0 +1,118 @@ +package de.tudresden.inf.mci.brailleplot.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +/** + * This class offers static methods for native library loading purposes. + * @author Georg Graßnick + * @version 2019.09.26 + */ +public final class NativeLibraryHelper { + + private static final Logger LOG = LoggerFactory.getLogger(NativeLibraryHelper.class); + private static final String LIB_PATH = calculateLibPath(); + + private NativeLibraryHelper() { } + + /** + * Loads a library from the resources according to the current system architecture and operating system. + * @param libName The name of the library. Library prefixes ({@literal "}lib{@literal "}) are not treated separately. + * The system specific file ending is added automatically and must not be included in the parameter. + * @return A File object representing the file of the library. The JNI is automatically set up to take of the library, + * so there is most likely no need for this return value. + * @throws NoSuchNativeLibraryException If the requested library could not be found or read. + */ + public static synchronized File loadNativeLibrary(final String libName) throws NoSuchNativeLibraryException { + File libFile; + try { + String libPath = "native/" + libName + "/" + LIB_PATH + "/" + libName + dynamicLibFileEnding(); + libFile = GeneralResource.getOrExportResourceFile(libPath).getAbsoluteFile(); + registerNewSystemLibPath(libFile.getParent()); + } catch (Exception e) { + throw new NoSuchNativeLibraryException("Could not provide native library from java resources", e); + } + LOG.debug("Found and exported native library \"" + libFile + "\" for requested library \"" + libName + "\""); + return libFile; + } + + /** + * Adds a path in the file system to the search path of the JNI. + * @param path The path to add. + */ + private static synchronized void registerNewSystemLibPath(final String path) { + String currentLibPath = System.getProperty("jna.library.path"); + String newLibPath = null; + boolean pathExists = false; + + if (currentLibPath == null) { + newLibPath = path; + } else { + // Do not insert path if it already is included + String[] existingPaths = currentLibPath.split(File.pathSeparator); + for (String s : existingPaths) { + if (s.equals(path)) { + pathExists = true; + break; + } + } + newLibPath = currentLibPath + File.pathSeparator + path; + } + + if (!pathExists) { + LOG.debug("Setting JNI library path property to \"" + newLibPath + "\""); + System.setProperty("jna.library.path", newLibPath); + } + } + + private static String calculateLibPath() { + return getArch() + "/" + getOs(); + } + + private static String getArch() { + String arch = System.getProperty("os.arch"); + // reference: https://stackoverflow.com/a/36926327 + switch (arch) { + case "x86": + case "i386": + case "i486": + case "i586": + case "i686": + return "x86_32"; + case "x86_64": + case "amd64": + return "x86_64"; + default: + throw new RuntimeException("Operating System architecture \"" + arch + "\" is currently not supported"); + } + } + + private static String getOs() { + String name = System.getProperty("os.name"); + String nameLow = name.toLowerCase(); + if (nameLow.contains("linux")) { + return "linux"; + } else if (nameLow.contains("mac")) { + return "osx"; + } else if (nameLow.contains("win")) { + return "win32"; + } else { + throw new RuntimeException("Operating System \"" + name + "\" is currently not supported"); + } + } + + private static String dynamicLibFileEnding() { + switch (getOs()) { + case "win32": + return ".dll"; + case "linux": + return ".so"; + case "osx": + return ".dylib"; + default: + throw new IllegalStateException("If this exception was thrown, something is wrong with your code"); + } + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/util/NoSuchNativeLibraryException.java b/src/main/java/de/tudresden/inf/mci/brailleplot/util/NoSuchNativeLibraryException.java new file mode 100644 index 0000000000000000000000000000000000000000..2f7331e5f542838d14a77027252710f5dd426b50 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/util/NoSuchNativeLibraryException.java @@ -0,0 +1,23 @@ +package de.tudresden.inf.mci.brailleplot.util; + +/** + * Indicates, some error has occurred while trying to load a native library from the java resources. + * @author Georg Graßnick + * @version 2019.09.26 + */ +public class NoSuchNativeLibraryException extends RuntimeException { + + public NoSuchNativeLibraryException() { } + + public NoSuchNativeLibraryException(final String message) { + super(message); + } + + public NoSuchNativeLibraryException(final Throwable cause) { + super(cause); + } + + public NoSuchNativeLibraryException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/third_party/liblouis_bin b/third_party/liblouis_bin new file mode 160000 index 0000000000000000000000000000000000000000..24b6908d0b167ce785c0f9ce8295d9845b69a303 --- /dev/null +++ b/third_party/liblouis_bin @@ -0,0 +1 @@ +Subproject commit 24b6908d0b167ce785c0f9ce8295d9845b69a303