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 header; + + @Expose + List> data; + + public TableContent(int x, int y) { + super(x, y); + } + + @Override + protected void addContentToPdf(PDRectangle rect, PDPageContentStream cos) throws IOException, ConfigurationException { + cos.beginText(); + cos.setFont(getFont(headerFont.getFontName()), headerFont.getFontSize()); + cos.newLineAtOffset(x,y); + } + + /* *** getter / setter *** */ + public Font getHeaderFont() { + return headerFont; + } + + public void setHeaderFont(Font headerFont) { + this.headerFont = headerFont; + } + +} diff --git a/pdf/src/main/java/de/muehlencord/shared/pdf/TextContent.java b/pdf/src/main/java/de/muehlencord/shared/pdf/TextContent.java new file mode 100644 index 0000000..8d9866c --- /dev/null +++ b/pdf/src/main/java/de/muehlencord/shared/pdf/TextContent.java @@ -0,0 +1,65 @@ +package de.muehlencord.shared.pdf; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.font.PDFont; + +/** + * + * @author jomu + */ +public class TextContent extends Content { + + private final List textLines; + + public TextContent() { + this(-1, -1); + } + + public TextContent(int x, int y) { + super(x, y); + this.textLines = new LinkedList<>(); + } + + public TextContent(int x, int y, String text) { + super(x, y); + this.textLines = new LinkedList<>(); + this.textLines.add(text); + } + + public TextContent addLine() { + this.textLines.add(""); + return this; + } + + public TextContent addLine(String text) { + this.textLines.add(text); + return this; + } + + public TextContent addLine(String text, String fontShortcut) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + /* *** getter / setter */ + public List getTextLines() { + return textLines; + } + + @Override + protected void addContentToPdf(PDRectangle rect, PDPageContentStream cos) throws IOException, ConfigurationException { + PDFont font = getFont(standardFont.getFontName()); + cos.beginText(); + cos.setFont(font, standardFont.getFontSize()); + cos.setLeading(standardFont.getFontSize() + standardFont.getPadding()); + cos.newLineAtOffset(x, y); + for (String line : textLines) { + cos.showText(line); + cos.newLine(); + } + cos.endText(); + } +} diff --git a/pdf/src/test/java/de/muehlencord/shared/pdf/ContentTest.java b/pdf/src/test/java/de/muehlencord/shared/pdf/ContentTest.java new file mode 100644 index 0000000..7cc5331 --- /dev/null +++ b/pdf/src/test/java/de/muehlencord/shared/pdf/ContentTest.java @@ -0,0 +1,24 @@ +package de.muehlencord.shared.pdf; + +import org.apache.pdfbox.pdmodel.font.PDFont; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author jomu + */ +public class ContentTest { + + public ContentTest() { + } + + @Test + public void testGetFont() throws ConfigurationException { + TextContent textContent = new TextContent(1,1,"Helvetica"); + PDFont font = textContent.getFont("Helvetica"); + assertEquals (PDType1Font.HELVETICA, font); + } + +} diff --git a/pdf/src/test/java/de/muehlencord/shared/pdf/PDFDocumentTest.java b/pdf/src/test/java/de/muehlencord/shared/pdf/PDFDocumentTest.java new file mode 100644 index 0000000..e06a505 --- /dev/null +++ b/pdf/src/test/java/de/muehlencord/shared/pdf/PDFDocumentTest.java @@ -0,0 +1,53 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package de.muehlencord.shared.pdf; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author jomu + */ +public class PDFDocumentTest { + + private static Gson gson; + + @BeforeClass + public static void setUpClass() { + gson = GsonUtil.getGsonInstance(); + } + + @Test + public void testToJson() { + System.out.println("testToJson"); + PDFDocument doc = new PDFDocument(); + doc.setPaperSize(PaperSize.A4); + TextContent addressContent = new TextContent(40, 692, "Max Mustermann") + .addLine("Musterstraße 123") + .addLine("12345 Musterhausen"); + doc.addContent(addressContent); + + TextContent invoiceInfoInformation = new TextContent(40, 442, "Sehr geehrter Anzeigenkunde, ") + .addLine() + .addLine() + .addLine("Wir danken für den Auftrag und bitten um Erledigung der folgenden Anzeigenabrechnung"); + doc.addContent(invoiceInfoInformation); + + TextContent informationContent = new TextContent(400, 662); + informationContent.addFont("bold", new Font("Helvetica-Bold", 12, 2)); + informationContent.addLine ("Anzeigenabrechnung", "bold"); + + System.out.println(gson.toJson(doc)); + } + +} diff --git a/pdf/src/test/java/de/muehlencord/shared/pdf/PDFTemplateTest.java b/pdf/src/test/java/de/muehlencord/shared/pdf/PDFTemplateTest.java new file mode 100644 index 0000000..660324a --- /dev/null +++ b/pdf/src/test/java/de/muehlencord/shared/pdf/PDFTemplateTest.java @@ -0,0 +1,31 @@ +package de.muehlencord.shared.pdf; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateExceptionHandler; +import java.io.File; +import java.io.IOException; +import org.junit.Test; + +/** + * + * @author jomu + */ +public class PDFTemplateTest { + + @Test + // @Ignore // TODO get template from test resources + public void testCreate() throws IOException, ConfigurationException { + Configuration cfg = new Configuration(Configuration.VERSION_2_3_24); + cfg.setDirectoryForTemplateLoading(new File("c:/temp")); + cfg.setDefaultEncoding("UTF-8"); + cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + cfg.setLogTemplateExceptions(false); + + Template template = cfg.getTemplate("test.ftlh"); + + PDFTemplate doc = new PDFTemplate(template); + doc.create ("c:/temp/test.pdf"); + } + +} diff --git a/pdf/src/test/resources/log4j.xml b/pdf/src/test/resources/log4j.xml new file mode 100644 index 0000000..9e9f5d7 --- /dev/null +++ b/pdf/src/test/resources/log4j.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +