magMerge is a small tool aimed at magazine publishers. It converts PDF files to double-paged images, so you can use them for your website or social media.
The tool is written in both Java and Kotlin - GUI is done by Java, Image Processing is done by Kotlin.
You can find the source code at GitHub.com/Skn0tt/magMerge.
What is Kotlin?
Kotlin is a new & shiny language that is very fun to write. It is built a bit like JavaScript, except that it features typing - what a godsend! 😅
Let’s look at some example Code:
// The main function is simply defined as main(), it doesn't have to be in a class
fun main(args: Array<String>) {
val g = greet() // Constant values are assigned as 'val'
var x = 5 // Variables are assigned as 'var'
x = 6 // They are reassignable
// You can print to stdout by calling `print`
println(g)
println(add(5, 6))
}
// function return types are specified by colons
fun greet(): String {
return "Greetings to you, my friend!"
}
// simple functions can be one-liners
// argument types are specified by colons, again
fun add(a: Int, b: Int) = a + b
Output:
Greetings to you, my friend!
11
6
The cool thing about Kotlin is its simplicity.
You write merely any of the boilerplate that’s needed in Java, and you don’t lose any of the cool stuff about Java:
Kotlin is a JVM Language.
That means that it compiles to the same .class
files as Java and can be run everywhere Java can run.
It can also be mixed and matched with Java without a problem - Kotlin can call Java code and Java can call Kotlin Code.
That also means that you can use all Java Libraries with Kotlin as well.
Now that we saw some generic Kotlin code, let’s look at the Image Processing logic! :D
Image Processing
fun getBufferedImagesForPDF(src: File) : List<BufferedImage> {
val doc: PDDocument = PDDocument.load(src)
val pdfRenderer = PDFRenderer(doc)
return (0 until doc.numberOfPages).map { pdfRenderer.renderImage(it) }
}
This function returns a list of BufferedImage
, one for each page of the passed File.
To do that, uses Apache’s PDFBox.
In line 5, you can see a cool feature of Kotlin: (0 until doc.numberOfPages)
creates a set where is the number of pages the PDF document has.
.map
then iterates over this set and creates a list of all values the attached block returns.
In our case, it’s the respective rendered image of the PDF document.
fun mergeImages(img1: BufferedImage, img2: BufferedImage) : BufferedImage {
val result = BufferedImage(img1.width + img2.width, img1.height, BufferedImage.TYPE_INT_RGB)
val g: Graphics = result.graphics
g.drawImage(img1, 0,0, null)
g.drawImage(img2, img1.width, 0, null)
return result
}
This function takes in two images and returns them horizontally merged.
It creates a new BufferedImage
of final width and height (Line 2).
It then writes the passed images to their respective locations (Line 5, 6) and returns the final image.
The computed BufferedImage
objects are then written back to the disk with JDK’s javax.imageio.ImageIO
.
Building with Gradle
What is Gradle?
Gradle is a build System - just like GNU Make or Grunt.
You can use it to automate compiling, packaging and running your code.
To customize it, you can write your own buildscripts in either Java, Kotlin or Groovy.
The interesting thing about Gradle is that it is extensible by plugins so you don’t need to reinvent the wheel.
Creating a fat Jar
fatJar
is Java-Spoke for a jarfile that bundles its dependencies, come name: they’re fat in size.
To do that, I used the Shadow
Gradle Plugin.
It automatically bundles all dependecies and depends on the configuration in the normal Gradle jar
task.
jar {
manifest {
attributes 'Main-Class': 'MagMerge'
}
}
The only important thing that needs to be in the manifest is the 'Main-Class'
-Attribute, which defines which file’s main
-routine is the entrypoint to the application.
Packaging as macOS app
I wanted this Project to be packaged as a normal application for macOS.
A quick Google search found out that there’s a Gradle plugin exactly for that: macAppBundle.
Since I already used Gradle for managing the project dependencies (PDFBox, jai-imageio) and building the fatJar
, this was perfect!
How does this work? It creates a macOS application folder and packages the Jar-archives as well as a Java Runtime Envorinment - so that it can run even when there’s no Java installed on the computer.
Here you can see the task config:
macAppBundle {
mainClassName = "MagMerge"
bundleJRE = true
jreHome = '/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home'
icon = "res/logo.icns"
javaProperties.put("apple.laf.useScreenMenuBar", "true")
runtimeConfigurationName = 'shadow'
jarTask = 'shadowJar'
}
mainClassName
does the same as Main-Class
in the above example: It defines the entrypoint.
Line 3 specifies that we want the JRE bundled with the application, Line 4 says where to copy it from.
Line 5 sets the app icon, Line 6 specifies that we want the app to use the MenuBar.
Line 7, 8 configure which tasks macAppBundle
calls in order to create the app.
Typing gradle createApp
in the terminal then creates the app for you.