Closes #77: Metadata in PDF export

This commit is contained in:
jendib 2016-03-15 00:43:27 +01:00
parent c2a2e9f585
commit 00ee2d3bf6
4 changed files with 228 additions and 19 deletions

View File

@ -7,6 +7,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.imageio.ImageIO;
@ -17,6 +19,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
@ -29,8 +32,11 @@ import org.odftoolkit.odfdom.doc.OdfTextDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Strings;
import com.google.common.io.Closer;
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.util.pdf.PdfPage;
import com.sismics.util.ImageUtil;
import com.sismics.util.mime.MimeType;
@ -141,21 +147,16 @@ public class PdfUtil {
/**
* Convert a document and its files to a merged PDF file.
*
* @param documentDto Document DTO
* @param fileList List of files
* @param fitImageToPage Fit images to the page
* @param metadata Add a page with metadata
* @param comment Add a page with comments
* @param margin Margins in millimeters
* @return PDF input stream
* @throws IOException
*/
public static InputStream convertToPdf(List<File> fileList, boolean fitImageToPage, boolean metadata, boolean comments, int margin) throws Exception {
// TODO PDF Export: Option to add a front page with:
// document title, document description, creator, date created, language,
// additional dublincore metadata (except relations)
// list of all files (and information if it is in this document or not)
// TODO PDF Export: Option to add the comments
public static InputStream convertToPdf(DocumentDto documentDto, List<File> fileList,
boolean fitImageToPage, boolean metadata, int margin) throws Exception {
// Setup PDFBox
Closer closer = Closer.create();
MemoryUsageSetting memUsageSettings = MemoryUsageSetting.setupMixed(1000000); // 1MB max memory usage
@ -166,12 +167,45 @@ public class PdfUtil {
try (PDDocument doc = new PDDocument(memUsageSettings)) {
// Add metadata
if (metadata) {
}
// Add comments
if (comments) {
PDPage page = new PDPage();
doc.addPage(page);
try (PdfPage pdfPage = new PdfPage(doc, page, margin * mmPerInch, PDType1Font.HELVETICA, 12)) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
pdfPage.addText(documentDto.getTitle(), true, PDType1Font.HELVETICA_BOLD, 16)
.newLine()
.addText("Created by " + documentDto.getCreator()
+ " on " + dateFormat.format(new Date(documentDto.getCreateTimestamp())), true)
.newLine()
.addText(documentDto.getDescription())
.newLine();
if (!Strings.isNullOrEmpty(documentDto.getSubject())) {
pdfPage.addText("Subject: " + documentDto.getSubject());
}
if (!Strings.isNullOrEmpty(documentDto.getIdentifier())) {
pdfPage.addText("Identifier: " + documentDto.getIdentifier());
}
if (!Strings.isNullOrEmpty(documentDto.getPublisher())) {
pdfPage.addText("Publisher: " + documentDto.getPublisher());
}
if (!Strings.isNullOrEmpty(documentDto.getFormat())) {
pdfPage.addText("Format: " + documentDto.getFormat());
}
if (!Strings.isNullOrEmpty(documentDto.getSource())) {
pdfPage.addText("Source: " + documentDto.getSource());
}
if (!Strings.isNullOrEmpty(documentDto.getType())) {
pdfPage.addText("Type: " + documentDto.getType());
}
if (!Strings.isNullOrEmpty(documentDto.getCoverage())) {
pdfPage.addText("Coverage: " + documentDto.getCoverage());
}
if (!Strings.isNullOrEmpty(documentDto.getRights())) {
pdfPage.addText("Rights: " + documentDto.getRights());
}
pdfPage.addText("Language: " + documentDto.getLanguage())
.newLine()
.addText("Files in this document : " + fileList.size(), false, PDType1Font.HELVETICA_BOLD, 12);
}
}
// Add files

View File

@ -0,0 +1,153 @@
package com.sismics.docs.core.util.pdf;
import java.io.Closeable;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
/**
* Wrapper around PDFBox for high level abstraction of PDF writing.
*
* @author bgamard
*/
public class PdfPage implements Closeable {
private PDPage pdPage;
private PDPageContentStream pdContent;
private float margin;
private PDFont defaultFont;
private int defaultFontSize;
/**
* Create a wrapper around a PDF page.
*
* @param pdDoc Document
* @param pdPage Page
* @param margin Margin
* @param defaultFont Default font
* @param defaultFontSize Default fond size
* @throws IOException
*/
public PdfPage(PDDocument pdDoc, PDPage pdPage, float margin, PDFont defaultFont, int defaultFontSize) throws IOException {
this.pdPage = pdPage;
this.pdContent = new PDPageContentStream(pdDoc, pdPage);
this.margin = margin;
this.defaultFont = defaultFont;
this.defaultFontSize = defaultFontSize;
pdContent.beginText();
pdContent.newLineAtOffset(margin, pdPage.getMediaBox().getHeight() - margin);
}
/**
* Write a text with default font.
*
* @param text Text
* @throws IOException
*/
public PdfPage addText(String text) throws IOException {
drawText(pdPage.getMediaBox().getWidth() - 2 * margin, defaultFont, defaultFontSize, text, false);
return this;
}
/**
* Write a text with default font.
*
* @param text Text
* @param centered If true, the text will be centered in the page
* @throws IOException
*/
public PdfPage addText(String text, boolean centered) throws IOException {
drawText(pdPage.getMediaBox().getWidth() - 2 * margin, defaultFont, defaultFontSize, text, centered);
return this;
}
/**
* Write a text in the page.
*
* @param text Text
* @param centered If true, the text will be centered in the page
* @param font Font
* @param fontSize Font size
* @throws IOException
*/
public PdfPage addText(String text, boolean centered, PDFont font, int fontSize) throws IOException {
drawText(pdPage.getMediaBox().getWidth() - 2 * margin, font, fontSize, text, centered);
return this;
}
/**
* Create a new line.
*
* @throws IOException
*/
public PdfPage newLine() throws IOException {
pdContent.newLineAtOffset(0, - defaultFont.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * defaultFontSize);
return this;
}
/**
* Draw a text with low level PDFBox API.
*
* @param paragraphWidth Paragraph width
* @param font Font
* @param fontSize Font size
* @param text Text
* @param centered If true, the text will be centered in the paragraph
* @throws IOException
*/
private void drawText(float paragraphWidth, PDFont font, int fontSize, String text, boolean centered) throws IOException {
pdContent.setFont(font, fontSize);
int start = 0;
int end = 0;
float height = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;
for (int i : possibleWrapPoints(text)) {
float width = font.getStringWidth(text.substring(start, i)) / 1000 * fontSize;
if (start < end && width > paragraphWidth) {
// Draw partial text and increase height
pdContent.newLineAtOffset(0, - height);
String line = text.substring(start, end);
float lineWidth = font.getStringWidth(line) / 1000 * fontSize;
float offset = (paragraphWidth - lineWidth) / 2;
if (centered) pdContent.newLineAtOffset(offset, 0);
pdContent.showText(line);
if (centered) pdContent.newLineAtOffset(- offset, 0);
start = end;
}
end = i;
}
// Last piece of text
String line = text.substring(start);
float lineWidth = font.getStringWidth(line) / 1000 * fontSize;
float offset = (paragraphWidth - lineWidth) / 2;
pdContent.newLineAtOffset(0, - height);
if (centered) pdContent.newLineAtOffset(offset, 0);
pdContent.showText(line);
if (centered) pdContent.newLineAtOffset(- offset, 0);
}
/**
* Returns wrap points for a given piece of text.
*
* @param text Text
* @return Wrap points
*/
private int[] possibleWrapPoints(String text) {
String[] split = text.split("(?<=\\W)");
int[] ret = new int[split.length];
ret[0] = split[0].length();
for (int i = 1 ; i < split.length ; i++) {
ret[i] = ret[i-1] + split[i].length();
}
return ret;
}
@Override
public void close() throws IOException {
pdContent.endText();
pdContent.close();
}
}

View File

@ -1,18 +1,22 @@
package com.sismics.docs.core.util;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Date;
import org.junit.Assert;
import org.junit.Test;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import com.google.common.io.Resources;
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.util.mime.MimeType;
import org.junit.Assert;
/**
* Test of the file entity utilities.
*
@ -50,6 +54,21 @@ public class TestFileUtil {
InputStream inputStream2 = Resources.getResource("file/udhr_encrypted.pdf").openStream();
InputStream inputStream3 = Resources.getResource("file/document.docx").openStream();
InputStream inputStream4 = Resources.getResource("file/document.odt").openStream()) {
// Document
DocumentDto documentDto = new DocumentDto();
documentDto.setTitle("My super document 1");
documentDto.setDescription("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis id turpis iaculis, commodo est ac, efficitur quam. Nam accumsan magna in orci vulputate ultricies. Sed vulputate neque magna, at laoreet leo ultricies vel. Proin eu hendrerit felis. Quisque sit amet arcu efficitur, pulvinar orci sed, imperdiet elit. Nunc posuere ex sed fermentum congue. Aliquam ultrices convallis finibus. Praesent iaculis justo vitae dictum auctor. Praesent suscipit imperdiet erat ac maximus. Aenean pharetra quam sed fermentum commodo. Donec sagittis ipsum nibh, id congue dolor venenatis quis. In tincidunt nisl non ex sollicitudin, a imperdiet neque scelerisque. Nullam lacinia ac orci sed faucibus. Donec tincidunt venenatis justo, nec fermentum justo rutrum a.");
documentDto.setSubject("A set of random picture");
documentDto.setIdentifier("ID-2016-08-00001");
documentDto.setPublisher("My Publisher, Inc.");
documentDto.setFormat("A4 standard ISO format");
documentDto.setType("Image");
documentDto.setCoverage("France");
documentDto.setRights("Public Domain");
documentDto.setLanguage("en");
documentDto.setCreator("user1");
documentDto.setCreateTimestamp(new Date().getTime());
// First file
Files.copy(inputStream0, DirectoryUtil.getStorageDirectory().resolve("apollo_landscape"), StandardCopyOption.REPLACE_EXISTING);
File file0 = new File();
@ -81,7 +100,10 @@ public class TestFileUtil {
file4.setId("document_odt");
file4.setMimeType(MimeType.OPEN_DOCUMENT_TEXT);
PdfUtil.convertToPdf(Lists.newArrayList(file0, file1, file2, file3, file4), true, true, true, 10).close();
try (InputStream pdfInputStream = PdfUtil.convertToPdf(documentDto, Lists.newArrayList(file0, file1, file2, file3, file4), true, true, 10);
OutputStream fileOutputStream = Files.newOutputStream(Paths.get("c:/temp.pdf"))) {
ByteStreams.copy(pdfInputStream, fileOutputStream);
}
}
}
}

View File

@ -206,7 +206,7 @@ public class DocumentResource extends BaseResource {
// Get document and check read permission
DocumentDao documentDao = new DocumentDao();
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, shareId == null ? principal.getId() : shareId);
final DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, shareId == null ? principal.getId() : shareId);
if (documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
}
@ -226,7 +226,7 @@ public class DocumentResource extends BaseResource {
StreamingOutput stream = new StreamingOutput() {
@Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
try (InputStream inputStream = PdfUtil.convertToPdf(fileList, fitImageToPage, metadata, comments, margin)) {
try (InputStream inputStream = PdfUtil.convertToPdf(documentDto, fileList, fitImageToPage, metadata, margin)) {
ByteStreams.copy(inputStream, outputStream);
} catch (Exception e) {
throw new IOException(e);