1 package fr.tiogars.inception.domains.domaintemplate.services;
2
3 import java.io.ByteArrayOutputStream;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.util.List;
7
8 import org.apache.pdfbox.pdmodel.PDDocument;
9 import org.apache.pdfbox.pdmodel.PDPage;
10 import org.apache.pdfbox.pdmodel.PDPageContentStream;
11 import org.apache.pdfbox.pdmodel.common.PDRectangle;
12 import org.apache.pdfbox.pdmodel.font.PDType0Font;
13 import org.springframework.http.HttpStatus;
14 import org.springframework.stereotype.Component;
15 import org.springframework.web.server.ResponseStatusException;
16
17 import fr.tiogars.inception.domains.domaintemplate.models.DomainTemplate;
18
19
20 @Component
21 public class DomainTemplatePdfRenderer {
22
23 private static final String FONT_RESOURCE = "/org/apache/pdfbox/resources/ttf/LiberationSans-Regular.ttf";
24 private static final float MARGIN = 48;
25 private static final float FONT_SIZE = 11;
26 private static final float LINE_HEIGHT = 16;
27 private static final float TEXT_WIDTH = PDRectangle.A4.getWidth() - 2 * MARGIN;
28
29
30
31
32
33
34
35
36
37 public byte[] render(String title, List<DomainTemplate> templates) throws IOException {
38 try (PDDocument document = new PDDocument();
39 InputStream fontStream = DomainTemplatePdfRenderer.class.getResourceAsStream(FONT_RESOURCE);
40 ByteArrayOutputStream output = new ByteArrayOutputStream()) {
41 if (fontStream == null) {
42 throw new IOException("The bundled PDF font is unavailable.");
43 }
44 PDType0Font font = PDType0Font.load(document, fontStream);
45 document.getDocumentInformation().setTitle(title);
46 try (PageWriter writer = new PageWriter(document, font, title)) {
47 if (templates.isEmpty()) {
48 writer.newPage();
49 writer.field("Records", "No DomainTemplates.");
50 }
51 for (DomainTemplate template : templates) {
52 writer.newPage();
53 writer.field("ID", template.id());
54 writer.field("Code", template.code());
55 writer.field("Label", template.label());
56 writer.field("Description", template.description());
57 writer.field("Created", template.createdDate());
58 writer.field("Created by", template.createdBy());
59 writer.field("Last modified", template.lastModifiedDate());
60 writer.field("Last modified by", template.lastModifiedBy());
61 }
62 }
63 document.save(output);
64 return output.toByteArray();
65 }
66 }
67
68 private static final class PageWriter implements AutoCloseable {
69
70 private final PDDocument document;
71 private final PDType0Font font;
72 private final String title;
73 private PDPageContentStream stream;
74 private float y;
75
76 private PageWriter(PDDocument document, PDType0Font font, String title) {
77 this.document = document;
78 this.font = font;
79 this.title = title;
80 }
81
82 private void newPage() throws IOException {
83 close();
84 PDPage page = new PDPage(PDRectangle.A4);
85 document.addPage(page);
86 stream = new PDPageContentStream(document, page);
87 text(title, 18, page.getMediaBox().getHeight() - MARGIN);
88 text("Page " + document.getNumberOfPages(), 9, MARGIN / 2);
89 y = page.getMediaBox().getHeight() - MARGIN - 36;
90 }
91
92 private void field(String label, Object value) throws IOException {
93 String content = label + ": " + (value == null ? "Not available" : value);
94 for (String paragraph : content.replace("\t", " ").split("\\R", -1)) {
95 StringBuilder line = new StringBuilder();
96 float width = 0;
97 for (int codePoint : paragraph.codePoints().toArray()) {
98 String character = new String(Character.toChars(codePoint));
99 float characterWidth = characterWidth(character);
100 if (width + characterWidth > TEXT_WIDTH && !line.isEmpty()) {
101 String currentLine = line.toString();
102 int wordBreak = currentLine.lastIndexOf(' ');
103 line(wordBreak > 0 ? currentLine.substring(0, wordBreak) : currentLine);
104 line.setLength(0);
105 if (wordBreak > 0) {
106 line.append(currentLine.substring(wordBreak + 1));
107 }
108 width = font.getStringWidth(line.toString()) * FONT_SIZE / 1000;
109 }
110 line.append(character);
111 width += characterWidth;
112 }
113 line(line.toString());
114 }
115 y -= LINE_HEIGHT / 2;
116 }
117
118 private float characterWidth(String character) throws IOException {
119 try {
120 return font.getStringWidth(character) * FONT_SIZE / 1000;
121 } catch (IllegalArgumentException exception) {
122 throw new ResponseStatusException(HttpStatus.UNPROCESSABLE_CONTENT,
123 "A DomainTemplate contains characters unsupported by the PDF font.", exception);
124 }
125 }
126
127 private void line(String value) throws IOException {
128 if (y < MARGIN) {
129 newPage();
130 }
131 text(value, FONT_SIZE, y);
132 y -= LINE_HEIGHT;
133 }
134
135 private void text(String value, float size, float baseline) throws IOException {
136 stream.beginText();
137 stream.setFont(font, size);
138 stream.newLineAtOffset(MARGIN, baseline);
139 stream.showText(value);
140 stream.endText();
141 }
142
143 @Override
144 public void close() throws IOException {
145 if (stream != null) {
146 stream.close();
147 stream = null;
148 }
149 }
150 }
151 }