diff --git a/pdf/pom.xml b/pdf/pom.xml
new file mode 100644
index 0000000..ce07676
--- /dev/null
+++ b/pdf/pom.xml
@@ -0,0 +1,67 @@
+
+
+ 4.0.0
+
+ de.muehlencord
+ shared
+ 1.0-SNAPSHOT
+
+
+ de.muehlencord.shared
+ shared-pdf
+ 1.0-SNAPSHOT
+ jar
+
+
+ org.freemarker
+ freemarker
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ org.hamcrest
+ hamcrest-core
+ 1.3
+ test
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.apache.pdfbox
+ pdfbox
+
+
+ com.google.code.gson
+ gson
+
+
+ org.slf4j
+ slf4j-log4j12
+ test
+
+
+ org.seleniumhq.selenium
+ selenium-java
+ test
+ 2.44.0
+
+
+ com.opera
+ operadriver
+ test
+ 1.5
+
+
+ org.seleniumhq.selenium
+ selenium-remote-driver
+
+
+
+
+
\ No newline at end of file
diff --git a/pdf/src/main/java/de/muehlencord/shared/pdf/ConfigurationException.java b/pdf/src/main/java/de/muehlencord/shared/pdf/ConfigurationException.java
new file mode 100644
index 0000000..7737e8b
--- /dev/null
+++ b/pdf/src/main/java/de/muehlencord/shared/pdf/ConfigurationException.java
@@ -0,0 +1,37 @@
+package de.muehlencord.shared.pdf;
+
+/**
+ *
+ * @author jomu
+ */
+public class ConfigurationException extends Exception {
+
+ /**
+ * Creates a new instance of ConfigurationException without
+ * detail message.
+ */
+ public ConfigurationException() {
+ }
+
+ /**
+ * Constructs an instance of ConfigurationException with the
+ * specified detail message.
+ *
+ * @param msg the detail message.
+ */
+ public ConfigurationException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an instance of ConfigurationException with the
+ * specified detail message and the given root cause.
+ *
+ * @param msg the detail message.
+ * @param th the root cause.
+ */
+ public ConfigurationException(String msg, Throwable th) {
+ super(msg);
+ }
+
+}
diff --git a/pdf/src/main/java/de/muehlencord/shared/pdf/Content.java b/pdf/src/main/java/de/muehlencord/shared/pdf/Content.java
new file mode 100644
index 0000000..52528e0
--- /dev/null
+++ b/pdf/src/main/java/de/muehlencord/shared/pdf/Content.java
@@ -0,0 +1,79 @@
+package de.muehlencord.shared.pdf;
+
+import com.google.gson.annotations.Expose;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+
+/**
+ *
+ * @author jomu
+ */
+public abstract class Content {
+
+ @Expose
+ protected int x;
+
+ @Expose
+ protected int y;
+
+ @Expose
+ protected Font standardFont;
+
+ @Expose
+ protected Map fontMap;
+
+ public Content(int x, int y) {
+ this.standardFont = new Font("Helvetica", 11);
+ this.x = x;
+ this.y = y;
+ this.fontMap = null;
+ }
+
+ public void addFont(String name, Font font) {
+ if (fontMap == null) {
+ fontMap = new ConcurrentHashMap<>();
+ }
+ fontMap.put(name, font);
+ }
+
+ protected PDFont getFont(String fontName) throws ConfigurationException {
+ if (fontName.equals(PDType1Font.HELVETICA.getBaseFont())) {
+ return PDType1Font.HELVETICA;
+ } else {
+ throw new ConfigurationException("Font " + fontName + " not supported");
+ }
+ }
+
+ protected abstract void addContentToPdf(PDRectangle rect, PDPageContentStream cos) throws IOException, ConfigurationException;
+
+ /* *** getter / setter *** */
+ public int getX() {
+ return x;
+ }
+
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public void setY(int y) {
+ this.y = y;
+ }
+
+ public Font getStandardFont() {
+ return standardFont;
+ }
+
+ public void setStandardFont(Font standardFont) {
+ this.standardFont = standardFont;
+ }
+
+}
diff --git a/pdf/src/main/java/de/muehlencord/shared/pdf/Font.java b/pdf/src/main/java/de/muehlencord/shared/pdf/Font.java
new file mode 100644
index 0000000..98e08df
--- /dev/null
+++ b/pdf/src/main/java/de/muehlencord/shared/pdf/Font.java
@@ -0,0 +1,51 @@
+package de.muehlencord.shared.pdf;
+
+/**
+ *
+ * @author jomu
+ */
+public class Font {
+
+ private String fontName;
+ private int fontSize;
+ private int padding;
+
+ public Font(String fontName, int fontSize) {
+ this.fontName = fontName;
+ this.fontSize = fontSize;
+ this.padding = 2;
+ }
+
+ public Font(String fontName, int fontSize, int padding) {
+ this.fontName = fontName;
+ this.fontSize = fontSize;
+ this.padding = padding;
+ }
+
+ /* *** getter / setter *** */
+
+ public String getFontName() {
+ return fontName;
+ }
+
+ public void setFontName(String fontName) {
+ this.fontName = fontName;
+ }
+
+ public int getFontSize() {
+ return fontSize;
+ }
+
+ public void setFontSize(int fontSize) {
+ this.fontSize = fontSize;
+ }
+
+ public int getPadding() {
+ return padding;
+ }
+
+ public void setPadding(int padding) {
+ this.padding = padding;
+ }
+
+}
diff --git a/pdf/src/main/java/de/muehlencord/shared/pdf/GsonUtil.java b/pdf/src/main/java/de/muehlencord/shared/pdf/GsonUtil.java
new file mode 100644
index 0000000..b2213ee
--- /dev/null
+++ b/pdf/src/main/java/de/muehlencord/shared/pdf/GsonUtil.java
@@ -0,0 +1,19 @@
+package de.muehlencord.shared.pdf;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ *
+ * @author jomu
+ */
+public class GsonUtil {
+
+ public final static Gson getGsonInstance() {
+ return new GsonBuilder()
+ .setPrettyPrinting()
+ .registerTypeAdapter(Content.class, new InterfaceAdapter<>())
+ .create();
+ }
+
+}
diff --git a/pdf/src/main/java/de/muehlencord/shared/pdf/InterfaceAdapter.java b/pdf/src/main/java/de/muehlencord/shared/pdf/InterfaceAdapter.java
new file mode 100644
index 0000000..89bfec5
--- /dev/null
+++ b/pdf/src/main/java/de/muehlencord/shared/pdf/InterfaceAdapter.java
@@ -0,0 +1,54 @@
+package de.muehlencord.shared.pdf;
+
+import com.google.gson.JsonDeserializationContext;
+import java.lang.reflect.Type;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ *
+ * @author jomu
+ */
+public class InterfaceAdapter implements JsonSerializer, JsonDeserializer {
+
+ @Override
+ public final JsonElement serialize(final T object, final Type interfaceType, final JsonSerializationContext context) {
+ final JsonObject member = new JsonObject();
+ member.addProperty("type", object.getClass().getName());
+ member.add("data", context.serialize(object));
+ return member;
+ }
+
+ @Override
+ public final T deserialize(final JsonElement elem, final Type interfaceType, final JsonDeserializationContext context) throws JsonParseException {
+ final JsonObject member = (JsonObject) elem;
+ final JsonElement typeString = get(member, "type");
+ final JsonElement data = get(member, "data");
+ final Type actualType = typeForName(typeString);
+
+ return context.deserialize(data, actualType);
+ }
+
+ private Type typeForName(final JsonElement typeElem) {
+ try {
+ return Class.forName(typeElem.getAsString());
+ } catch (ClassNotFoundException e) {
+ throw new JsonParseException(e);
+ }
+ }
+
+ private JsonElement get(final JsonObject wrapper, final String memberName) {
+ final JsonElement elem = wrapper.get(memberName);
+
+ if (elem == null) {
+ throw new JsonParseException(
+ "no '" + memberName + "' member found in json file.");
+ }
+ return elem;
+ }
+
+}
diff --git a/pdf/src/main/java/de/muehlencord/shared/pdf/PDFDocument.java b/pdf/src/main/java/de/muehlencord/shared/pdf/PDFDocument.java
new file mode 100644
index 0000000..fe729cf
--- /dev/null
+++ b/pdf/src/main/java/de/muehlencord/shared/pdf/PDFDocument.java
@@ -0,0 +1,43 @@
+package de.muehlencord.shared.pdf;
+
+import com.google.gson.annotations.Expose;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author jomu
+ */
+public class PDFDocument {
+
+ @Expose
+ private PaperSize paperSize;
+
+ @Expose
+ private final List contentList;
+
+ public PDFDocument() {
+ this.contentList = new ArrayList<>();
+ }
+
+ public PDFDocument addContent (Content content) {
+ contentList.add (content);
+ return this;
+ }
+
+ /* *** getter / setter *** */
+
+ public PaperSize getPaperSize() {
+ return paperSize;
+ }
+
+ public void setPaperSize(PaperSize paperSize) {
+ this.paperSize = paperSize;
+ }
+
+ public List getContentList() {
+ return contentList;
+ }
+
+
+}
diff --git a/pdf/src/main/java/de/muehlencord/shared/pdf/PDFTemplate.java b/pdf/src/main/java/de/muehlencord/shared/pdf/PDFTemplate.java
new file mode 100644
index 0000000..25f32af
--- /dev/null
+++ b/pdf/src/main/java/de/muehlencord/shared/pdf/PDFTemplate.java
@@ -0,0 +1,75 @@
+package de.muehlencord.shared.pdf;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author jomu
+ */
+public class PDFTemplate {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PDFTemplate.class);
+
+ public final static String A4 = "A4";
+
+ private final Template template;
+ private final ConcurrentHashMap dataModel;
+
+ public PDFTemplate(Template template) {
+ this.template = template;
+ this.dataModel = new ConcurrentHashMap<>();
+ }
+
+ public void create(String filenName) throws ConfigurationException, IOException {
+ Writer out = new StringWriter();
+ try {
+ template.process(dataModel, out);
+ } catch (TemplateException ex) {
+ throw new IOException("Error while processing template", ex);
+ }
+ String json = out.toString();
+ LOGGER.info(json);
+
+ Gson gson = GsonUtil.getGsonInstance();
+ PDFDocument pdfDoc = gson.fromJson(json, PDFDocument.class);
+
+ PDDocument doc = new PDDocument();
+
+ PDPage page;
+ switch (pdfDoc.getPaperSize()) {
+ case A4:
+ page = new PDPage(PDRectangle.A4);
+ break;
+ default:
+ throw new ConfigurationException("Papersize " + pdfDoc.getPaperSize().getLabel() + " not supported");
+ }
+ doc.addPage(page);
+
+ PDRectangle rect = page.getMediaBox();
+ PDPageContentStream cos = new PDPageContentStream(doc, page, AppendMode.APPEND, false);
+ for (Content content : pdfDoc.getContentList()) {
+ content.addContentToPdf(rect, cos);
+ }
+ cos.close();
+ doc.save(filenName);
+ }
+
+ void applyTemplate(Template template) {
+ throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+ }
+
+}
diff --git a/pdf/src/main/java/de/muehlencord/shared/pdf/TableContent.java b/pdf/src/main/java/de/muehlencord/shared/pdf/TableContent.java
new file mode 100644
index 0000000..f1d1dc0
--- /dev/null
+++ b/pdf/src/main/java/de/muehlencord/shared/pdf/TableContent.java
@@ -0,0 +1,44 @@
+package de.muehlencord.shared.pdf;
+
+import com.google.gson.annotations.Expose;
+import java.io.IOException;
+import java.util.List;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+
+/**
+ *
+ * @author jomu
+ */
+public class TableContent extends Content {
+
+ @Expose
+ Font headerFont;
+
+ @Expose
+ List