mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-22 07:25:04 +00:00
Fixed inaccessible attachments, clean up
This commit is contained in:
parent
244cbe36ff
commit
f16ddeb583
@ -27,5 +27,5 @@ dependencies {
|
|||||||
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
|
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
|
||||||
api 'org.snakeyaml:snakeyaml-engine:2.9'
|
api 'org.snakeyaml:snakeyaml-engine:2.9'
|
||||||
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9"
|
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9"
|
||||||
api 'jakarta.mail:jakarta.mail-api:2.1.3'
|
api 'com.sun.mail:jakarta.mail:2.0.1'
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.common.util;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
|
import static stirling.software.common.util.PDFAttachmentUtils.setCatalogViewerPreferences;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -20,10 +22,7 @@ import java.util.Properties;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.apache.pdfbox.cos.COSDictionary;
|
|
||||||
import org.apache.pdfbox.cos.COSName;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
@ -43,7 +42,8 @@ import lombok.experimental.UtilityClass;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.common.model.api.converters.EmlToPdfRequest;
|
import stirling.software.common.model.api.converters.EmlToPdfRequest;
|
||||||
import static stirling.software.common.util.PDFAttachmentUtils.setCatalogViewerPreferences;
|
import stirling.software.common.model.api.converters.HTMLToPdfRequest;
|
||||||
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@UtilityClass
|
@UtilityClass
|
||||||
@ -197,8 +197,7 @@ public class EmlToPdf {
|
|||||||
boolean disableSanitize)
|
boolean disableSanitize)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
stirling.software.common.model.api.converters.HTMLToPdfRequest htmlRequest =
|
HTMLToPdfRequest htmlRequest = createHtmlRequest(request);
|
||||||
createHtmlRequest(request);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return FileToPdf.convertHtmlToPdf(
|
return FileToPdf.convertHtmlToPdf(
|
||||||
@ -882,33 +881,33 @@ public class EmlToPdf {
|
|||||||
Class<?> messageClass = message.getClass();
|
Class<?> messageClass = message.getClass();
|
||||||
|
|
||||||
// Extract headers via reflection
|
// Extract headers via reflection
|
||||||
java.lang.reflect.Method getSubject = messageClass.getMethod("getSubject");
|
Method getSubject = messageClass.getMethod("getSubject");
|
||||||
String subject = (String) getSubject.invoke(message);
|
String subject = (String) getSubject.invoke(message);
|
||||||
content.setSubject(subject != null ? safeMimeDecode(subject) : "No Subject");
|
content.setSubject(subject != null ? safeMimeDecode(subject) : "No Subject");
|
||||||
|
|
||||||
java.lang.reflect.Method getFrom = messageClass.getMethod("getFrom");
|
Method getFrom = messageClass.getMethod("getFrom");
|
||||||
Object[] fromAddresses = (Object[]) getFrom.invoke(message);
|
Object[] fromAddresses = (Object[]) getFrom.invoke(message);
|
||||||
content.setFrom(
|
content.setFrom(
|
||||||
fromAddresses != null && fromAddresses.length > 0
|
fromAddresses != null && fromAddresses.length > 0
|
||||||
? safeMimeDecode(fromAddresses[0].toString())
|
? safeMimeDecode(fromAddresses[0].toString())
|
||||||
: "");
|
: "");
|
||||||
|
|
||||||
java.lang.reflect.Method getAllRecipients = messageClass.getMethod("getAllRecipients");
|
Method getAllRecipients = messageClass.getMethod("getAllRecipients");
|
||||||
Object[] recipients = (Object[]) getAllRecipients.invoke(message);
|
Object[] recipients = (Object[]) getAllRecipients.invoke(message);
|
||||||
content.setTo(
|
content.setTo(
|
||||||
recipients != null && recipients.length > 0
|
recipients != null && recipients.length > 0
|
||||||
? safeMimeDecode(recipients[0].toString())
|
? safeMimeDecode(recipients[0].toString())
|
||||||
: "");
|
: "");
|
||||||
|
|
||||||
java.lang.reflect.Method getSentDate = messageClass.getMethod("getSentDate");
|
Method getSentDate = messageClass.getMethod("getSentDate");
|
||||||
content.setDate((Date) getSentDate.invoke(message));
|
content.setDate((Date) getSentDate.invoke(message));
|
||||||
|
|
||||||
// Extract content
|
// Extract content
|
||||||
java.lang.reflect.Method getContent = messageClass.getMethod("getContent");
|
Method getContent = messageClass.getMethod("getContent");
|
||||||
Object messageContent = getContent.invoke(message);
|
Object messageContent = getContent.invoke(message);
|
||||||
|
|
||||||
if (messageContent instanceof String stringContent) {
|
if (messageContent instanceof String stringContent) {
|
||||||
java.lang.reflect.Method getContentType = messageClass.getMethod("getContentType");
|
Method getContentType = messageClass.getMethod("getContentType");
|
||||||
String contentType = (String) getContentType.invoke(message);
|
String contentType = (String) getContentType.invoke(message);
|
||||||
if (contentType != null && contentType.toLowerCase().contains("text/html")) {
|
if (contentType != null && contentType.toLowerCase().contains("text/html")) {
|
||||||
content.setHtmlBody(stringContent);
|
content.setHtmlBody(stringContent);
|
||||||
@ -947,11 +946,10 @@ public class EmlToPdf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Class<?> multipartClass = multipart.getClass();
|
Class<?> multipartClass = multipart.getClass();
|
||||||
java.lang.reflect.Method getCount = multipartClass.getMethod("getCount");
|
Method getCount = multipartClass.getMethod("getCount");
|
||||||
int count = (Integer) getCount.invoke(multipart);
|
int count = (Integer) getCount.invoke(multipart);
|
||||||
|
|
||||||
java.lang.reflect.Method getBodyPart =
|
Method getBodyPart = multipartClass.getMethod("getBodyPart", int.class);
|
||||||
multipartClass.getMethod("getBodyPart", int.class);
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
Object part = getBodyPart.invoke(multipart, i);
|
Object part = getBodyPart.invoke(multipart, i);
|
||||||
@ -972,12 +970,12 @@ public class EmlToPdf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Class<?> partClass = part.getClass();
|
Class<?> partClass = part.getClass();
|
||||||
java.lang.reflect.Method isMimeType = partClass.getMethod("isMimeType", String.class);
|
Method isMimeType = partClass.getMethod("isMimeType", String.class);
|
||||||
java.lang.reflect.Method getContent = partClass.getMethod("getContent");
|
Method getContent = partClass.getMethod("getContent");
|
||||||
java.lang.reflect.Method getDisposition = partClass.getMethod("getDisposition");
|
Method getDisposition = partClass.getMethod("getDisposition");
|
||||||
java.lang.reflect.Method getFileName = partClass.getMethod("getFileName");
|
Method getFileName = partClass.getMethod("getFileName");
|
||||||
java.lang.reflect.Method getContentType = partClass.getMethod("getContentType");
|
Method getContentType = partClass.getMethod("getContentType");
|
||||||
java.lang.reflect.Method getHeader = partClass.getMethod("getHeader", String.class);
|
Method getHeader = partClass.getMethod("getHeader", String.class);
|
||||||
|
|
||||||
Object disposition = getDisposition.invoke(part);
|
Object disposition = getDisposition.invoke(part);
|
||||||
String filename = (String) getFileName.invoke(part);
|
String filename = (String) getFileName.invoke(part);
|
||||||
@ -1184,7 +1182,7 @@ public class EmlToPdf {
|
|||||||
private static byte[] attachFilesToPdf(
|
private static byte[] attachFilesToPdf(
|
||||||
byte[] pdfBytes,
|
byte[] pdfBytes,
|
||||||
List<EmailAttachment> attachments,
|
List<EmailAttachment> attachments,
|
||||||
stirling.software.common.service.CustomPDFDocumentFactory pdfDocumentFactory)
|
CustomPDFDocumentFactory pdfDocumentFactory)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
try (PDDocument document = pdfDocumentFactory.load(pdfBytes);
|
try (PDDocument document = pdfDocumentFactory.load(pdfBytes);
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package stirling.software.common.util;
|
package stirling.software.common.util;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.pdfbox.cos.COSDictionary;
|
import org.apache.pdfbox.cos.COSDictionary;
|
||||||
import org.apache.pdfbox.cos.COSName;
|
import org.apache.pdfbox.cos.COSName;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
import org.apache.pdfbox.pdmodel.PageMode;
|
import org.apache.pdfbox.pdmodel.PageMode;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class PDFAttachmentUtils {
|
public class PDFAttachmentUtils {
|
||||||
|
|
||||||
@ -18,24 +19,29 @@ public class PDFAttachmentUtils {
|
|||||||
COSDictionary catalogDict = catalog.getCOSObject();
|
COSDictionary catalogDict = catalog.getCOSObject();
|
||||||
|
|
||||||
// Set PageMode to UseAttachments - this is the standard PDF specification approach
|
// Set PageMode to UseAttachments - this is the standard PDF specification approach
|
||||||
// PageMode values: UseNone, UseOutlines, UseThumbs, FullScreen, UseOC, UseAttachments
|
// PageMode values: UseNone, UseOutlines, UseThumbs, FullScreen, UseOC,
|
||||||
|
// UseAttachments
|
||||||
catalog.setPageMode(pageMode);
|
catalog.setPageMode(pageMode);
|
||||||
catalogDict.setName(COSName.PAGE_MODE, pageMode.stringValue());
|
catalogDict.setName(COSName.PAGE_MODE, pageMode.stringValue());
|
||||||
|
|
||||||
// Also set viewer preferences for better attachment viewing experience
|
// Also set viewer preferences for better attachment viewing experience
|
||||||
COSDictionary viewerPrefs = (COSDictionary) catalogDict.getDictionaryObject(COSName.VIEWER_PREFERENCES);
|
COSDictionary viewerPrefs =
|
||||||
|
(COSDictionary) catalogDict.getDictionaryObject(COSName.VIEWER_PREFERENCES);
|
||||||
if (viewerPrefs == null) {
|
if (viewerPrefs == null) {
|
||||||
viewerPrefs = new COSDictionary();
|
viewerPrefs = new COSDictionary();
|
||||||
catalogDict.setItem(COSName.VIEWER_PREFERENCES, viewerPrefs);
|
catalogDict.setItem(COSName.VIEWER_PREFERENCES, viewerPrefs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set NonFullScreenPageMode to UseAttachments as fallback for viewers that support it
|
// Set NonFullScreenPageMode to UseAttachments as fallback for viewers that support
|
||||||
viewerPrefs.setName(COSName.getPDFName("NonFullScreenPageMode"), pageMode.stringValue());
|
// it
|
||||||
|
viewerPrefs.setName(
|
||||||
|
COSName.getPDFName("NonFullScreenPageMode"), pageMode.stringValue());
|
||||||
|
|
||||||
// Additional viewer preferences that may help with attachment display
|
// Additional viewer preferences that may help with attachment display
|
||||||
viewerPrefs.setBoolean(COSName.getPDFName("DisplayDocTitle"), true);
|
viewerPrefs.setBoolean(COSName.getPDFName("DisplayDocTitle"), true);
|
||||||
|
|
||||||
log.info("Set PDF PageMode to UseAttachments to automatically show attachments pane");
|
log.info(
|
||||||
|
"Set PDF PageMode to UseAttachments to automatically show attachments pane");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Log error but don't fail the entire operation for viewer preferences
|
// Log error but don't fail the entire operation for viewer preferences
|
||||||
|
@ -44,8 +44,7 @@ public class WebResponseUtils {
|
|||||||
headers.setContentType(mediaType);
|
headers.setContentType(mediaType);
|
||||||
headers.setContentLength(bytes.length);
|
headers.setContentLength(bytes.length);
|
||||||
String encodedDocName =
|
String encodedDocName =
|
||||||
URLEncoder.encode(docName, StandardCharsets.UTF_8)
|
URLEncoder.encode(docName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||||
.replaceAll("\\+", "%20");
|
|
||||||
headers.setContentDispositionFormData("attachment", encodedDocName);
|
headers.setContentDispositionFormData("attachment", encodedDocName);
|
||||||
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
|
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.common.model.ApplicationProperties;
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -83,9 +84,9 @@ public class EndpointConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void disableGroup(String group) {
|
public void disableGroup(String group) {
|
||||||
Set<String> endpoints = endpointGroups.get(group);
|
Set<String> disabledEndpoints = endpointGroups.get(group);
|
||||||
if (endpoints != null) {
|
if (disabledEndpoints != null) {
|
||||||
for (String endpoint : endpoints) {
|
for (String endpoint : disabledEndpoints) {
|
||||||
disableEndpoint(endpoint);
|
disableEndpoint(endpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import io.github.pixee.security.Filenames;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.service.AttachmentServiceInterface;
|
||||||
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RequestMapping("/api/v1/misc")
|
||||||
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
|
public class AttachmentController {
|
||||||
|
|
||||||
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
private final AttachmentServiceInterface pdfAttachmentService;
|
||||||
|
|
||||||
|
@SuppressWarnings("DataFlowIssue")
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/add-attachments")
|
||||||
|
@Operation(
|
||||||
|
summary = "Add attachments to PDF",
|
||||||
|
description =
|
||||||
|
"This endpoint adds embedded files (attachments) to a PDF and sets the PageMode to UseAttachments to make them visible. Input:PDF + Files Output:PDF Type:MISO")
|
||||||
|
public ResponseEntity<byte[]> addAttachments(
|
||||||
|
@RequestParam("fileInput") MultipartFile pdfFile,
|
||||||
|
@RequestParam("attachments") List<MultipartFile> attachments)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
PDDocument document =
|
||||||
|
pdfAttachmentService.addAttachment(
|
||||||
|
pdfDocumentFactory.load(pdfFile, false), attachments);
|
||||||
|
|
||||||
|
return WebResponseUtils.pdfDocToWebResponse(
|
||||||
|
document,
|
||||||
|
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
||||||
|
.replaceFirst("[.][^.]+$", "")
|
||||||
|
+ "_with_attachments.pdf");
|
||||||
|
}
|
||||||
|
}
|
@ -1,100 +0,0 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.*;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import io.github.pixee.security.Filenames;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.service.PDFAttachmentServiceInterface;
|
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
|
||||||
import stirling.software.common.util.WebResponseUtils;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@RestController
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@RequestMapping("/api/v1/misc")
|
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
|
||||||
public class AttachmentsController {
|
|
||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
|
||||||
|
|
||||||
private final PDFAttachmentServiceInterface pdfAttachmentService;
|
|
||||||
|
|
||||||
@SuppressWarnings("DataFlowIssue")
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-attachments")
|
|
||||||
@Operation(
|
|
||||||
summary = "Add attachments to PDF",
|
|
||||||
description =
|
|
||||||
"This endpoint adds embedded files (attachments) to a PDF and sets the PageMode to UseAttachments to make them visible. Input:PDF + Files Output:PDF Type:MISO")
|
|
||||||
public ResponseEntity<byte[]> addAttachments(
|
|
||||||
@RequestParam("fileInput") MultipartFile pdfFile,
|
|
||||||
@RequestParam("attachments") List<MultipartFile> attachments)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
// Load the PDF document
|
|
||||||
PDDocument document = pdfDocumentFactory.load(pdfFile, false);
|
|
||||||
|
|
||||||
// Get or create the document catalog
|
|
||||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
|
||||||
|
|
||||||
// Create embedded files name tree if it doesn't exist
|
|
||||||
PDDocumentNameDictionary documentNames = catalog.getNames();
|
|
||||||
PDEmbeddedFilesNameTreeNode embeddedFilesTree = new PDEmbeddedFilesNameTreeNode();
|
|
||||||
|
|
||||||
if (documentNames != null) {
|
|
||||||
embeddedFilesTree = documentNames.getEmbeddedFiles();
|
|
||||||
} else {
|
|
||||||
documentNames = new PDDocumentNameDictionary(catalog);
|
|
||||||
documentNames.setEmbeddedFiles(embeddedFilesTree);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add attachments
|
|
||||||
catalog.setNames(documentNames);
|
|
||||||
byte[] output =
|
|
||||||
pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
|
|
||||||
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
|
||||||
output,
|
|
||||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_with_attachments.pdf");
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-attachments")
|
|
||||||
@Operation(
|
|
||||||
summary = "Remove attachments from PDF",
|
|
||||||
description =
|
|
||||||
"This endpoint removes all embedded files (attachments) from a PDF. Input:PDF Output:PDF Type:SISO")
|
|
||||||
public ResponseEntity<byte[]> removeAttachments(
|
|
||||||
@RequestParam("fileInput") MultipartFile pdfFile) throws IOException {
|
|
||||||
|
|
||||||
// Load the PDF document and document catalog
|
|
||||||
PDDocument document = pdfDocumentFactory.load(pdfFile);
|
|
||||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
|
||||||
|
|
||||||
// Remove embedded files
|
|
||||||
if (catalog.getNames() != null) {
|
|
||||||
catalog.getNames().setEmbeddedFiles(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset PageMode to UseNone (default)
|
|
||||||
catalog.setPageMode(PageMode.USE_NONE);
|
|
||||||
|
|
||||||
// Return the modified PDF
|
|
||||||
return WebResponseUtils.pdfDocToWebResponse(
|
|
||||||
document,
|
|
||||||
Filenames.toSimpleFileName(pdfFile.getOriginalFilename())
|
|
||||||
.replaceFirst("[.][^.]+$", "")
|
|
||||||
+ "_attachments_removed.pdf");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF.service;
|
package stirling.software.SPDF.service;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import static stirling.software.common.util.PDFAttachmentUtils.setCatalogViewerPreferences;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -9,48 +10,53 @@ import java.util.Map;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
||||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||||
import org.apache.pdfbox.pdmodel.PageMode;
|
import org.apache.pdfbox.pdmodel.PageMode;
|
||||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
|
||||||
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
|
||||||
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import stirling.software.common.util.PDFAttachmentUtils;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class PDFAttachmentService implements PDFAttachmentServiceInterface {
|
public class AttachmentService implements AttachmentServiceInterface {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] addAttachment(
|
public PDDocument addAttachment(PDDocument document, List<MultipartFile> attachments)
|
||||||
PDDocument document,
|
|
||||||
PDEmbeddedFilesNameTreeNode embeddedFilesTree,
|
|
||||||
List<MultipartFile> attachments)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
||||||
|
PDDocumentNameDictionary documentNames = catalog.getNames();
|
||||||
|
PDEmbeddedFilesNameTreeNode embeddedFilesTree = new PDEmbeddedFilesNameTreeNode();
|
||||||
|
|
||||||
|
if (documentNames != null) {
|
||||||
|
embeddedFilesTree = documentNames.getEmbeddedFiles();
|
||||||
|
} else {
|
||||||
|
documentNames = new PDDocumentNameDictionary(catalog);
|
||||||
|
documentNames.setEmbeddedFiles(embeddedFilesTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog.setNames(documentNames);
|
||||||
Map<String, PDComplexFileSpecification> existingNames;
|
Map<String, PDComplexFileSpecification> existingNames;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
existingNames = embeddedFilesTree.getNames();
|
Map<String, PDComplexFileSpecification> originalNames = embeddedFilesTree.getNames();
|
||||||
|
|
||||||
if (existingNames == null) {
|
if (originalNames == null) {
|
||||||
log.debug("No existing embedded files found, creating new names map.");
|
log.debug("No existing embedded files found, creating new names map.");
|
||||||
existingNames = new HashMap<>();
|
existingNames = new HashMap<>();
|
||||||
|
} else {
|
||||||
|
existingNames = new HashMap<>(originalNames);
|
||||||
|
log.debug("Embedded files: {}", existingNames.keySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("Embedded files: {}", existingNames.keySet());
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Could not retrieve existing embedded files", e);
|
log.error("Could not retrieve existing embedded files", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
grantAccessPermissions(document);
|
|
||||||
final Map<String, PDComplexFileSpecification> existingEmbeddedFiles = existingNames;
|
|
||||||
|
|
||||||
attachments.forEach(
|
attachments.forEach(
|
||||||
attachment -> {
|
attachment -> {
|
||||||
String filename = attachment.getOriginalFilename();
|
String filename = attachment.getOriginalFilename();
|
||||||
@ -73,12 +79,10 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
|
|||||||
fileSpecification.setFile(filename);
|
fileSpecification.setFile(filename);
|
||||||
fileSpecification.setFileUnicode(filename);
|
fileSpecification.setFileUnicode(filename);
|
||||||
fileSpecification.setFileDescription("Embedded attachment: " + filename);
|
fileSpecification.setFileDescription("Embedded attachment: " + filename);
|
||||||
embeddedFile.setFile(fileSpecification);
|
|
||||||
fileSpecification.setEmbeddedFile(embeddedFile);
|
fileSpecification.setEmbeddedFile(embeddedFile);
|
||||||
fileSpecification.setEmbeddedFileUnicode(embeddedFile);
|
fileSpecification.setEmbeddedFileUnicode(embeddedFile);
|
||||||
|
|
||||||
// Add to the existing files map
|
existingNames.put(filename, fileSpecification);
|
||||||
existingEmbeddedFiles.put(filename, fileSpecification);
|
|
||||||
|
|
||||||
log.info("Added attachment: {} ({} bytes)", filename, attachment.getSize());
|
log.info("Added attachment: {} ({} bytes)", filename, attachment.getSize());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -87,39 +91,8 @@ public class PDFAttachmentService implements PDFAttachmentServiceInterface {
|
|||||||
});
|
});
|
||||||
|
|
||||||
embeddedFilesTree.setNames(existingNames);
|
embeddedFilesTree.setNames(existingNames);
|
||||||
PDFAttachmentUtils.setCatalogViewerPreferences(document, PageMode.USE_ATTACHMENTS);
|
setCatalogViewerPreferences(document, PageMode.USE_ATTACHMENTS);
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
||||||
document.save(output);
|
|
||||||
|
|
||||||
return output.toByteArray();
|
return document;
|
||||||
}
|
|
||||||
|
|
||||||
private void grantAccessPermissions(PDDocument document) {
|
|
||||||
try {
|
|
||||||
AccessPermission currentPermissions = document.getCurrentAccessPermission();
|
|
||||||
|
|
||||||
currentPermissions.setCanAssembleDocument(true);
|
|
||||||
currentPermissions.setCanFillInForm(currentPermissions.canFillInForm());
|
|
||||||
currentPermissions.setCanModify(true);
|
|
||||||
currentPermissions.setCanPrint(true);
|
|
||||||
currentPermissions.setCanPrintFaithful(true);
|
|
||||||
|
|
||||||
// Ensure these permissions are enabled for embedded file access
|
|
||||||
currentPermissions.setCanExtractContent(true);
|
|
||||||
currentPermissions.setCanExtractForAccessibility(true);
|
|
||||||
currentPermissions.setCanModifyAnnotations(true);
|
|
||||||
|
|
||||||
var protectionPolicy = new StandardProtectionPolicy(null, null, currentPermissions);
|
|
||||||
|
|
||||||
if (!document.isAllSecurityToBeRemoved()) {
|
|
||||||
document.setAllSecurityToBeRemoved(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.protect(protectionPolicy);
|
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
||||||
document.save(output);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package stirling.software.SPDF.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
public interface AttachmentServiceInterface {
|
||||||
|
|
||||||
|
PDDocument addAttachment(PDDocument document, List<MultipartFile> attachments)
|
||||||
|
throws IOException;
|
||||||
|
}
|
@ -1,17 +0,0 @@
|
|||||||
package stirling.software.SPDF.service;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
public interface PDFAttachmentServiceInterface {
|
|
||||||
|
|
||||||
byte[] addAttachment(
|
|
||||||
PDDocument document,
|
|
||||||
PDEmbeddedFilesNameTreeNode efTree,
|
|
||||||
List<MultipartFile> attachments)
|
|
||||||
throws IOException;
|
|
||||||
}
|
|
@ -1205,6 +1205,13 @@ addImage.everyPage=كل صفحة؟
|
|||||||
addImage.upload=إضافة صورة
|
addImage.upload=إضافة صورة
|
||||||
addImage.submit=إضافة صورة
|
addImage.submit=إضافة صورة
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=دمج
|
merge.title=دمج
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=قم بسحب المفات وإفلاتها هنا
|
fileChooser.hoveredDragAndDrop=قم بسحب المفات وإفلاتها هنا
|
||||||
fileChooser.extractPDF=جاري الاستخراج...
|
fileChooser.extractPDF=جاري الاستخراج...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1594,6 +1594,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Buraxılışlar
|
releases.footer=Buraxılışlar
|
||||||
|
@ -1594,6 +1594,7 @@ fileChooser.dragAndDropPDF=Влачете и пуснете PDF файл
|
|||||||
fileChooser.dragAndDropImage=Влачете и пуснете изображение
|
fileChooser.dragAndDropImage=Влачете и пуснете изображение
|
||||||
fileChooser.hoveredDragAndDrop=Влачете и пуснете файл(ове) тук
|
fileChooser.hoveredDragAndDrop=Влачете и пуснете файл(ове) тук
|
||||||
fileChooser.extractPDF=Извличане...
|
fileChooser.extractPDF=Извличане...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Версии
|
releases.footer=Версии
|
||||||
|
@ -1594,6 +1594,7 @@ fileChooser.dragAndDropPDF=PDF ཡིག་ཆ་འཐེན་ནས་འཇ
|
|||||||
fileChooser.dragAndDropImage=པར་རིས་ཡིག་ཆ་འཐེན་ནས་འཇོག་པ།
|
fileChooser.dragAndDropImage=པར་རིས་ཡིག་ཆ་འཐེན་ནས་འཇོག་པ།
|
||||||
fileChooser.hoveredDragAndDrop=ཡིག་ཆ་འདིར་འཐེན་ནས་འཇོག་པ།
|
fileChooser.hoveredDragAndDrop=ཡིག་ཆ་འདིར་འཐེན་ནས་འཇོག་པ།
|
||||||
fileChooser.extractPDF=འདོན་རིས་འགྱུར་བའི་སྒྲིག་བཏང་བ།
|
fileChooser.extractPDF=འདོན་རིས་འགྱུར་བའི་སྒྲིག་བཏང་བ།
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=པར་གཞི།
|
releases.footer=པར་གཞི།
|
||||||
|
@ -1594,7 +1594,7 @@ fileChooser.dragAndDropPDF=Arrossega i deixa anar un fitxer PDF
|
|||||||
fileChooser.dragAndDropImage=Arrossega i deixa anar un fitxer d'imatge
|
fileChooser.dragAndDropImage=Arrossega i deixa anar un fitxer d'imatge
|
||||||
fileChooser.hoveredDragAndDrop=Arrossega i deixa anar fitxer(s) aquí
|
fileChooser.hoveredDragAndDrop=Arrossega i deixa anar fitxer(s) aquí
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Llançaments
|
releases.footer=Llançaments
|
||||||
releases.title=Notes de Llançament
|
releases.title=Notes de Llançament
|
||||||
|
@ -1594,7 +1594,7 @@ fileChooser.dragAndDropPDF=Přetáhnout PDF soubor
|
|||||||
fileChooser.dragAndDropImage=Přetáhnout obrázek
|
fileChooser.dragAndDropImage=Přetáhnout obrázek
|
||||||
fileChooser.hoveredDragAndDrop=Přetáhněte soubor(y) sem
|
fileChooser.hoveredDragAndDrop=Přetáhněte soubor(y) sem
|
||||||
fileChooser.extractPDF=Extrahování...
|
fileChooser.extractPDF=Extrahování...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Vydání
|
releases.footer=Vydání
|
||||||
releases.title=Poznámky k vydání
|
releases.title=Poznámky k vydání
|
||||||
|
@ -1594,7 +1594,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
releases.title=Release Notes
|
releases.title=Release Notes
|
||||||
|
@ -1594,6 +1594,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF-Datei
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Bilddatei
|
fileChooser.dragAndDropImage=Drag & Drop Bilddatei
|
||||||
fileChooser.hoveredDragAndDrop=Datei(en) hierhin Ziehen & Fallenlassen
|
fileChooser.hoveredDragAndDrop=Datei(en) hierhin Ziehen & Fallenlassen
|
||||||
fileChooser.extractPDF=Extrahiere...
|
fileChooser.extractPDF=Extrahiere...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Veröffentlichungen
|
releases.footer=Veröffentlichungen
|
||||||
|
@ -1594,7 +1594,7 @@ fileChooser.dragAndDropPDF=Σύρετε & αφήστε αρχείο PDF
|
|||||||
fileChooser.dragAndDropImage=Σύρετε & αφήστε αρχείο εικόνας
|
fileChooser.dragAndDropImage=Σύρετε & αφήστε αρχείο εικόνας
|
||||||
fileChooser.hoveredDragAndDrop=Σύρετε & αφήστε αρχείο(α) εδώ
|
fileChooser.hoveredDragAndDrop=Σύρετε & αφήστε αρχείο(α) εδώ
|
||||||
fileChooser.extractPDF=Εξαγωγή...
|
fileChooser.extractPDF=Εξαγωγή...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Εκδόσεις
|
releases.footer=Εκδόσεις
|
||||||
releases.title=Σημειώσεις έκδοσης
|
releases.title=Σημειώσεις έκδοσης
|
||||||
|
@ -1212,13 +1212,9 @@ addImage.submit=Add image
|
|||||||
#attachments
|
#attachments
|
||||||
attachments.title=Add Attachments
|
attachments.title=Add Attachments
|
||||||
attachments.header=Add attachments
|
attachments.header=Add attachments
|
||||||
attachments.removeHeader=Remove attachments from PDF
|
|
||||||
attachments.selectFiles=Select files to attach
|
|
||||||
attachments.description=Allows you to add attachments to the PDF
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
attachments.addButton=Add Attachments
|
attachments.addButton=Add Attachments
|
||||||
attachments.removeDescription=This will remove all embedded files from the PDF.
|
|
||||||
attachments.removeButton=Remove All Attachments
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Merge
|
merge.title=Merge
|
||||||
@ -1608,6 +1604,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1210,15 +1210,11 @@ addImage.submit=Add image
|
|||||||
|
|
||||||
|
|
||||||
#attachments
|
#attachments
|
||||||
attachments.title=Attachments
|
attachments.title=Add Attachments
|
||||||
attachments.header=Add attachments
|
attachments.header=Add attachments
|
||||||
attachments.removeHeader=Remove attachments from PDF
|
|
||||||
attachments.selectFiles=Select files to attach
|
|
||||||
attachments.description=Allows you to add attachments to the PDF
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
attachments.addButton=Add Attachments
|
attachments.addButton=Add Attachments
|
||||||
attachments.removeDescription=This will remove all embedded files from the PDF.
|
|
||||||
attachments.removeButton=Remove All Attachments
|
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
@ -1609,6 +1605,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1205,6 +1205,12 @@ addImage.everyPage=¿Todas las páginas?
|
|||||||
addImage.upload=Añadir imagen
|
addImage.upload=Añadir imagen
|
||||||
addImage.submit=Enviar imagen
|
addImage.submit=Enviar imagen
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Unir
|
merge.title=Unir
|
||||||
@ -1594,6 +1600,7 @@ fileChooser.dragAndDropPDF=Arrastrar & Soltar archivo PDF
|
|||||||
fileChooser.dragAndDropImage=Arrastrar & Soltar archivo de Imagen
|
fileChooser.dragAndDropImage=Arrastrar & Soltar archivo de Imagen
|
||||||
fileChooser.hoveredDragAndDrop=Arrastrar & Soltar archivos(s) aquí
|
fileChooser.hoveredDragAndDrop=Arrastrar & Soltar archivos(s) aquí
|
||||||
fileChooser.extractPDF=Extrayendo...
|
fileChooser.extractPDF=Extrayendo...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Versiones
|
releases.footer=Versiones
|
||||||
|
@ -1594,6 +1594,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=هر صفحه؟
|
|||||||
addImage.upload=افزودن تصویر
|
addImage.upload=افزودن تصویر
|
||||||
addImage.submit=افزودن تصویر
|
addImage.submit=افزودن تصویر
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=ادغام
|
merge.title=ادغام
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید
|
fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید
|
||||||
fileChooser.extractPDF=در حال استخراج...
|
fileChooser.extractPDF=در حال استخراج...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=نسخهها
|
releases.footer=نسخهها
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=Toutes les pages ?
|
|||||||
addImage.upload=Télécharger une image
|
addImage.upload=Télécharger une image
|
||||||
addImage.submit=Ajouter une image
|
addImage.submit=Ajouter une image
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Fusionner
|
merge.title=Fusionner
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Glisser & Déposer le(s) fichier(s) ici
|
fileChooser.hoveredDragAndDrop=Glisser & Déposer le(s) fichier(s) ici
|
||||||
fileChooser.extractPDF=Extraction en cours...
|
fileChooser.extractPDF=Extraction en cours...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Versions
|
releases.footer=Versions
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=Gach Leathanach?
|
|||||||
addImage.upload=Cuir íomhá leis
|
addImage.upload=Cuir íomhá leis
|
||||||
addImage.submit=Cuir íomhá leis
|
addImage.submit=Cuir íomhá leis
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Cumaisc
|
merge.title=Cumaisc
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=Tarraing & Scaoil comhad PDF
|
|||||||
fileChooser.dragAndDropImage=Tarraing & Scaoil comhad Íomhá
|
fileChooser.dragAndDropImage=Tarraing & Scaoil comhad Íomhá
|
||||||
fileChooser.hoveredDragAndDrop=Tarraing agus scaoil comhad(í) anseo
|
fileChooser.hoveredDragAndDrop=Tarraing agus scaoil comhad(í) anseo
|
||||||
fileChooser.extractPDF=Ag Aistriú...
|
fileChooser.extractPDF=Ag Aistriú...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Eisiúintí
|
releases.footer=Eisiúintí
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=हर पृष्ठ?
|
|||||||
addImage.upload=छवि जोड़ें
|
addImage.upload=छवि जोड़ें
|
||||||
addImage.submit=छवि जोड़ें
|
addImage.submit=छवि जोड़ें
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=मर्ज करें
|
merge.title=मर्ज करें
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=PDF फ़ाइल खींचें और छो
|
|||||||
fileChooser.dragAndDropImage=छवि फ़ाइल खींचें और छोड़ें
|
fileChooser.dragAndDropImage=छवि फ़ाइल खींचें और छोड़ें
|
||||||
fileChooser.hoveredDragAndDrop=फ़ाइल(ें) यहाँ खींचें और छोड़ें
|
fileChooser.hoveredDragAndDrop=फ़ाइल(ें) यहाँ खींचें और छोड़ें
|
||||||
fileChooser.extractPDF=निकालना...
|
fileChooser.extractPDF=निकालना...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=रिलीज़
|
releases.footer=रिलीज़
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=Dodaj sliku
|
|||||||
addImage.submit=Dodaj sliku
|
addImage.submit=Dodaj sliku
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Spajanje
|
merge.title=Spajanje
|
||||||
merge.header=Spajanje više PDF-ova (2+)
|
merge.header=Spajanje više PDF-ova (2+)
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=Minden oldalra?
|
|||||||
addImage.upload=Kép hozzáadása
|
addImage.upload=Kép hozzáadása
|
||||||
addImage.submit=Kép hozzáadása
|
addImage.submit=Kép hozzáadása
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Egyesítés
|
merge.title=Egyesítés
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=Húzza ide a PDF fájlt
|
|||||||
fileChooser.dragAndDropImage=Húzza ide a képfájlt
|
fileChooser.dragAndDropImage=Húzza ide a képfájlt
|
||||||
fileChooser.hoveredDragAndDrop=Húzza ide a fájl(oka)t
|
fileChooser.hoveredDragAndDrop=Húzza ide a fájl(oka)t
|
||||||
fileChooser.extractPDF=Kinyerés...
|
fileChooser.extractPDF=Kinyerés...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Kiadási jegyzék
|
releases.footer=Kiadási jegyzék
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=Tambahkan Gambar
|
|||||||
addImage.submit=Tambahkan Gambar
|
addImage.submit=Tambahkan Gambar
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Gabungkan
|
merge.title=Gabungkan
|
||||||
merge.header=Gabungkan beberapa PDFs (2+)
|
merge.header=Gabungkan beberapa PDFs (2+)
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=Ogni pagina?
|
|||||||
addImage.upload=Aggiungi immagine
|
addImage.upload=Aggiungi immagine
|
||||||
addImage.submit=Aggiungi immagine
|
addImage.submit=Aggiungi immagine
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Unisci
|
merge.title=Unisci
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=Trascina & rilascia il file PDF
|
|||||||
fileChooser.dragAndDropImage=Trascina & rilascia il file immagine
|
fileChooser.dragAndDropImage=Trascina & rilascia il file immagine
|
||||||
fileChooser.hoveredDragAndDrop=Trascina & rilascia i file qui
|
fileChooser.hoveredDragAndDrop=Trascina & rilascia i file qui
|
||||||
fileChooser.extractPDF=Estraendo...
|
fileChooser.extractPDF=Estraendo...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Rilasci
|
releases.footer=Rilasci
|
||||||
|
@ -1205,6 +1205,12 @@ addImage.everyPage=全ページ?
|
|||||||
addImage.upload=画像の追加
|
addImage.upload=画像の追加
|
||||||
addImage.submit=画像の追加
|
addImage.submit=画像の追加
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=結合
|
merge.title=結合
|
||||||
@ -1594,6 +1600,7 @@ fileChooser.dragAndDropPDF=PDFファイルをドラッグ&ドロップ
|
|||||||
fileChooser.dragAndDropImage=画像ファイルをドラッグ&ドロップ
|
fileChooser.dragAndDropImage=画像ファイルをドラッグ&ドロップ
|
||||||
fileChooser.hoveredDragAndDrop=ファイルをここにドラッグ&ドロップ
|
fileChooser.hoveredDragAndDrop=ファイルをここにドラッグ&ドロップ
|
||||||
fileChooser.extractPDF=抽出中...
|
fileChooser.extractPDF=抽出中...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=リリース
|
releases.footer=リリース
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=이미지 추가
|
|||||||
addImage.submit=이미지 추가
|
addImage.submit=이미지 추가
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=병합
|
merge.title=병합
|
||||||
merge.header=여러 PDF 병합 (2개 이상)
|
merge.header=여러 PDF 병합 (2개 이상)
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=PDF 파일을 드래그 앤 드롭
|
|||||||
fileChooser.dragAndDropImage=이미지 파일을 드래그 앤 드롭
|
fileChooser.dragAndDropImage=이미지 파일을 드래그 앤 드롭
|
||||||
fileChooser.hoveredDragAndDrop=여기에 파일을 드래그 앤 드롭하세요
|
fileChooser.hoveredDragAndDrop=여기에 파일을 드래그 앤 드롭하세요
|
||||||
fileChooser.extractPDF=추출 중...
|
fileChooser.extractPDF=추출 중...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=릴리스
|
releases.footer=릴리스
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=എല്ലാ പേജിലും?
|
|||||||
addImage.upload=ചിത്രം ചേർക്കുക
|
addImage.upload=ചിത്രം ചേർക്കുക
|
||||||
addImage.submit=ചിത്രം ചേർക്കുക
|
addImage.submit=ചിത്രം ചേർക്കുക
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=ലയിപ്പിക്കുക
|
merge.title=ലയിപ്പിക്കുക
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=PDF ഫയൽ വലിച്ചിടുക
|
|||||||
fileChooser.dragAndDropImage=ചിത്ര ഫയൽ വലിച്ചിടുക
|
fileChooser.dragAndDropImage=ചിത്ര ഫയൽ വലിച്ചിടുക
|
||||||
fileChooser.hoveredDragAndDrop=ഫയൽ(കൾ) ഇവിടെ വലിച്ചിടുക
|
fileChooser.hoveredDragAndDrop=ഫയൽ(കൾ) ഇവിടെ വലിച്ചിടുക
|
||||||
fileChooser.extractPDF=വേർതിരിച്ചെടുക്കുന്നു...
|
fileChooser.extractPDF=വേർതിരിച്ചെടുക്കുന്നു...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=റിലീസുകൾ
|
releases.footer=റിലീസുകൾ
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=Afbeelding toevoegen
|
|||||||
addImage.submit=Afbeelding toevoegen
|
addImage.submit=Afbeelding toevoegen
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Samenvoegen
|
merge.title=Samenvoegen
|
||||||
merge.header=Meerdere PDF's samenvoegen (2+)
|
merge.header=Meerdere PDF's samenvoegen (2+)
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=På hver side?
|
|||||||
addImage.upload=Legg til bilde
|
addImage.upload=Legg til bilde
|
||||||
addImage.submit=Legg til bilde
|
addImage.submit=Legg til bilde
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Slå sammen
|
merge.title=Slå sammen
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Versjoner
|
releases.footer=Versjoner
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=Dodaj obraz
|
|||||||
addImage.submit=Dodaj obraz
|
addImage.submit=Dodaj obraz
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Połącz
|
merge.title=Połącz
|
||||||
merge.header=Połącz wiele dokumentów PDF (2+)
|
merge.header=Połącz wiele dokumentów PDF (2+)
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=Przeciągnij i upuść plik PDF
|
|||||||
fileChooser.dragAndDropImage=Przeciągnij i upuść plik obrazu
|
fileChooser.dragAndDropImage=Przeciągnij i upuść plik obrazu
|
||||||
fileChooser.hoveredDragAndDrop=Przeciągnij i upuść plik(i) tutaj
|
fileChooser.hoveredDragAndDrop=Przeciągnij i upuść plik(i) tutaj
|
||||||
fileChooser.extractPDF=Trwa wyodrębnianie...
|
fileChooser.extractPDF=Trwa wyodrębnianie...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Wydania
|
releases.footer=Wydania
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=Carregar imagem
|
|||||||
addImage.submit=Adicionar imagem
|
addImage.submit=Adicionar imagem
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Mesclar
|
merge.title=Mesclar
|
||||||
merge.header=Mesclar
|
merge.header=Mesclar
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=Arraste & Solte PDF(s)
|
|||||||
fileChooser.dragAndDropImage=Arraste & Solte Imagem(ns)
|
fileChooser.dragAndDropImage=Arraste & Solte Imagem(ns)
|
||||||
fileChooser.hoveredDragAndDrop=Arraste & Solte arquivo(s) aqui
|
fileChooser.hoveredDragAndDrop=Arraste & Solte arquivo(s) aqui
|
||||||
fileChooser.extractPDF=Extraindo...
|
fileChooser.extractPDF=Extraindo...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Versões
|
releases.footer=Versões
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=Adicionar imagem
|
|||||||
addImage.submit=Adicionar imagem
|
addImage.submit=Adicionar imagem
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Juntar
|
merge.title=Juntar
|
||||||
merge.header=Juntar múltiplos PDFs (2+)
|
merge.header=Juntar múltiplos PDFs (2+)
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=Arrastar e Largar ficheiro PDF
|
|||||||
fileChooser.dragAndDropImage=Arrastar e Largar ficheiro de Imagem
|
fileChooser.dragAndDropImage=Arrastar e Largar ficheiro de Imagem
|
||||||
fileChooser.hoveredDragAndDrop=Arrastar e Largar ficheiro(s) aqui
|
fileChooser.hoveredDragAndDrop=Arrastar e Largar ficheiro(s) aqui
|
||||||
fileChooser.extractPDF=Extraindo...
|
fileChooser.extractPDF=Extraindo...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Lançamentos
|
releases.footer=Lançamentos
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=Adăugare imagine
|
|||||||
addImage.submit=Adăugare imagine
|
addImage.submit=Adăugare imagine
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Unire
|
merge.title=Unire
|
||||||
merge.header=Unirea mai multor PDF-uri (2+)
|
merge.header=Unirea mai multor PDF-uri (2+)
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=Каждая страница?
|
|||||||
addImage.upload=Добавить изображение
|
addImage.upload=Добавить изображение
|
||||||
addImage.submit=Добавить изображение
|
addImage.submit=Добавить изображение
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Объединить
|
merge.title=Объединить
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=Перетащите PDF-файл
|
|||||||
fileChooser.dragAndDropImage=Перетащите файл изображения
|
fileChooser.dragAndDropImage=Перетащите файл изображения
|
||||||
fileChooser.hoveredDragAndDrop=Перетащите файл(ы) сюда
|
fileChooser.hoveredDragAndDrop=Перетащите файл(ы) сюда
|
||||||
fileChooser.extractPDF=Извлечение...
|
fileChooser.extractPDF=Извлечение...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Релизы
|
releases.footer=Релизы
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=Každá stránka?
|
|||||||
addImage.upload=Pridať obrázok
|
addImage.upload=Pridať obrázok
|
||||||
addImage.submit=Pridať obrázok
|
addImage.submit=Pridať obrázok
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Zlúčiť
|
merge.title=Zlúčiť
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=Dodaj sliko
|
|||||||
addImage.submit=Dodaj sliko
|
addImage.submit=Dodaj sliko
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Združi
|
merge.title=Združi
|
||||||
merge.header=Združi več PDF-jev (2+)
|
merge.header=Združi več PDF-jev (2+)
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=Povleci in spusti datoteko PDF
|
|||||||
fileChooser.dragAndDropImage=Povleci in spusti slikovno datoteko
|
fileChooser.dragAndDropImage=Povleci in spusti slikovno datoteko
|
||||||
fileChooser.hoveredDragAndDrop=Povleci in spusti datoteko(e) sem
|
fileChooser.hoveredDragAndDrop=Povleci in spusti datoteko(e) sem
|
||||||
fileChooser.extractPDF=Izvlečenje...
|
fileChooser.extractPDF=Izvlečenje...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Izdaje
|
releases.footer=Izdaje
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=Dodaj sliku
|
|||||||
addImage.submit=Dodaj sliku
|
addImage.submit=Dodaj sliku
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Spajanje
|
merge.title=Spajanje
|
||||||
merge.header=Spajanje više PDF fajlova (2+)
|
merge.header=Spajanje više PDF fajlova (2+)
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=Varje sida?
|
|||||||
addImage.upload=Lägg till bild
|
addImage.upload=Lägg till bild
|
||||||
addImage.submit=Lägg till bild
|
addImage.submit=Lägg till bild
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Sammanfoga
|
merge.title=Sammanfoga
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=Dra & Släpp PDF fil
|
|||||||
fileChooser.dragAndDropImage=Dra & Släpp bildfil
|
fileChooser.dragAndDropImage=Dra & Släpp bildfil
|
||||||
fileChooser.hoveredDragAndDrop=Dra & Släpp fil(er) här
|
fileChooser.hoveredDragAndDrop=Dra & Släpp fil(er) här
|
||||||
fileChooser.extractPDF=Extraherar...
|
fileChooser.extractPDF=Extraherar...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Utgåvor
|
releases.footer=Utgåvor
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=ทุกหน้า?
|
|||||||
addImage.upload=เพิ่มรูปภาพ
|
addImage.upload=เพิ่มรูปภาพ
|
||||||
addImage.submit=เพิ่มรูปภาพ
|
addImage.submit=เพิ่มรูปภาพ
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=รวม
|
merge.title=รวม
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1205,6 +1205,13 @@ addImage.everyPage=Her Sayfa mı?
|
|||||||
addImage.upload=Resim ekle
|
addImage.upload=Resim ekle
|
||||||
addImage.submit=Resim ekle
|
addImage.submit=Resim ekle
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Birleştir
|
merge.title=Birleştir
|
||||||
@ -1594,6 +1601,7 @@ fileChooser.dragAndDropPDF=PDF dosyasını Sürükle & Bırak
|
|||||||
fileChooser.dragAndDropImage=Görsel dosyasını Sürükle & Bırak
|
fileChooser.dragAndDropImage=Görsel dosyasını Sürükle & Bırak
|
||||||
fileChooser.hoveredDragAndDrop=Dosya(lar)ı buraya sürükleyip bırakın
|
fileChooser.hoveredDragAndDrop=Dosya(lar)ı buraya sürükleyip bırakın
|
||||||
fileChooser.extractPDF=PDF Çıkarılıyor...
|
fileChooser.extractPDF=PDF Çıkarılıyor...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Sürümler
|
releases.footer=Sürümler
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=Додати зображення
|
|||||||
addImage.submit=Додати зображення
|
addImage.submit=Додати зображення
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Об'єднати
|
merge.title=Об'єднати
|
||||||
merge.header=Об'єднання кількох PDF-файлів (2+)
|
merge.header=Об'єднання кількох PDF-файлів (2+)
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=Перетащите PDF-файл
|
|||||||
fileChooser.dragAndDropImage=Перетащите файл зображення
|
fileChooser.dragAndDropImage=Перетащите файл зображення
|
||||||
fileChooser.hoveredDragAndDrop=Перетащите файл(и) сюда
|
fileChooser.hoveredDragAndDrop=Перетащите файл(и) сюда
|
||||||
fileChooser.extractPDF=Видобування...
|
fileChooser.extractPDF=Видобування...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Релізи
|
releases.footer=Релізи
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=Thêm hình ảnh
|
|||||||
addImage.submit=Thêm hình ảnh
|
addImage.submit=Thêm hình ảnh
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Trộn
|
merge.title=Trộn
|
||||||
merge.header=Trộn nhiều PDF (2+)
|
merge.header=Trộn nhiều PDF (2+)
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=Drag & Drop PDF file
|
|||||||
fileChooser.dragAndDropImage=Drag & Drop Image file
|
fileChooser.dragAndDropImage=Drag & Drop Image file
|
||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
|
||||||
fileChooser.extractPDF=Extracting...
|
fileChooser.extractPDF=Extracting...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=Releases
|
releases.footer=Releases
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=添加图片
|
|||||||
addImage.submit=添加图片
|
addImage.submit=添加图片
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=合并
|
merge.title=合并
|
||||||
merge.header=合并多个 PDF(2个以上)。
|
merge.header=合并多个 PDF(2个以上)。
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=拖放PDF文件
|
|||||||
fileChooser.dragAndDropImage=拖放图片文件
|
fileChooser.dragAndDropImage=拖放图片文件
|
||||||
fileChooser.hoveredDragAndDrop=拖放文件到此处
|
fileChooser.hoveredDragAndDrop=拖放文件到此处
|
||||||
fileChooser.extractPDF=处理中...
|
fileChooser.extractPDF=处理中...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=版本
|
releases.footer=版本
|
||||||
|
@ -1206,6 +1206,14 @@ addImage.upload=新增圖片
|
|||||||
addImage.submit=新增圖片
|
addImage.submit=新增圖片
|
||||||
|
|
||||||
|
|
||||||
|
#attachments
|
||||||
|
attachments.title=Add Attachments
|
||||||
|
attachments.header=Add attachments
|
||||||
|
attachments.description=Allows you to add attachments to the PDF
|
||||||
|
attachments.descriptionPlaceholder=Enter a description for the attachments...
|
||||||
|
attachments.addButton=Add Attachments
|
||||||
|
|
||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=合併
|
merge.title=合併
|
||||||
merge.header=合併多個 PDF
|
merge.header=合併多個 PDF
|
||||||
@ -1594,6 +1602,7 @@ fileChooser.dragAndDropPDF=拖放 PDF 檔案
|
|||||||
fileChooser.dragAndDropImage=拖放圖片檔案
|
fileChooser.dragAndDropImage=拖放圖片檔案
|
||||||
fileChooser.hoveredDragAndDrop=將檔案拖放至此
|
fileChooser.hoveredDragAndDrop=將檔案拖放至此
|
||||||
fileChooser.extractPDF=處理中...
|
fileChooser.extractPDF=處理中...
|
||||||
|
fileChooser.addAttachments=drag & drop attachments here
|
||||||
|
|
||||||
#release notes
|
#release notes
|
||||||
releases.footer=版本資訊
|
releases.footer=版本資訊
|
||||||
|
@ -6,6 +6,9 @@ class FileIconFactory {
|
|||||||
return this.createPDFIcon();
|
return this.createPDFIcon();
|
||||||
case "csv":
|
case "csv":
|
||||||
return this.createCSVIcon();
|
return this.createCSVIcon();
|
||||||
|
case "xls":
|
||||||
|
case "xlsx":
|
||||||
|
return this.createXLSXIcon();
|
||||||
case "jpe":
|
case "jpe":
|
||||||
case "jpg":
|
case "jpg":
|
||||||
case "jpeg":
|
case "jpeg":
|
||||||
@ -44,8 +47,29 @@ class FileIconFactory {
|
|||||||
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor"><path d="M216-144q-30 0-51-21.5T144-216v-528q0-29 21-50.5t51-21.5h528q30 0 51 21.5t21 50.5v528q0 29-21 50.5T744-144H216Zm48-144h432L552-480 444-336l-72-96-108 144Z"/></svg>`;
|
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor"><path d="M216-144q-30 0-51-21.5T144-216v-528q0-29 21-50.5t51-21.5h528q30 0 51 21.5t21 50.5v528q0 29-21 50.5T744-144H216Zm48-144h432L552-480 444-336l-72-96-108 144Z"/></svg>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createCSVIcon() {
|
||||||
|
return `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-filetype-csv" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M14 4.5V14a2 2 0 0 1-2 2h-1v-1h1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5zM3.517 14.841a1.13 1.13 0 0 0 .401.823q.195.162.478.252.284.091.665.091.507 0 .859-.158.354-.158.539-.54.185-.382.185-.816 0-.335-.123-.628a1.4 1.4 0 0 0-.366-.486 1.8 1.8 0 0 0-.614-.314 2.8 2.8 0 0 0-.865-.118 2.1 2.1 0 0 0-.614.094 1.4 1.4 0 0 0-.471.264 1.1 1.1 0 0 0-.298.429.9.9 0 0 0-.103.539h.606a.4.4 0 0 1 .096-.258.5.5 0 0 1 .213-.164.6.6 0 0 1 .33-.082.7.7 0 0 1 .458.132.4.4 0 0 1 .153.372.4.4 0 0 1-.085.235.7.7 0 0 1-.25.192 1.4 1.4 0 0 1-.407.115c-.127.023-.266.05-.416.081a1.8 1.8 0 0 0-.534.187 1.2 1.2 0 0 0-.382.346 1 1 0 0 0-.138.537q0 .295.101.517M8.717 14.841a1.13 1.13 0 0 0 .401.823q.195.162.478.252.284.091.665.091.507 0 .859-.158.354-.158.539-.54.185-.382.185-.816 0-.335-.123-.628a1.4 1.4 0 0 0-.366-.486 1.8 1.8 0 0 0-.614-.314 2.8 2.8 0 0 0-.865-.118 2.1 2.1 0 0 0-.614.094 1.4 1.4 0 0 0-.471.264 1.1 1.1 0 0 0-.298.429.9.9 0 0 0-.103.539h.606a.4.4 0 0 1 .096-.258.5.5 0 0 1 .213-.164.6.6 0 0 1 .33-.082.7.7 0 0 1 .458.132.4.4 0 0 1 .153.372.4.4 0 0 1-.085.235.7.7 0 0 1-.25.192 1.4 1.4 0 0 1-.407.115c-.127.023-.266.05-.416.081a1.8 1.8 0 0 0-.534.187 1.2 1.2 0 0 0-.382.346 1 1 0 0 0-.138.537q0 .295.101.517M14.229 13.12v.506H11.85v-.506h1.063v-1.277H11.85v-.506h2.379v.506h-1.063z"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createXLSXIcon() {
|
||||||
|
return `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-file-earmark-excel" viewBox="0 0 16 16">
|
||||||
|
<path d="M5.884 6.68a.5.5 0 1 0-.768.64L7.349 10l-2.233 2.68a.5.5 0 0 0 .768.64L8 10.781l2.116 2.54a.5.5 0 0 0 .768-.641L8.651 10l2.233-2.68a.5.5 0 0 0-.768-.64L8 9.219l-2.116-2.54z"/>
|
||||||
|
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
static createUnknownFileIcon() {
|
static createUnknownFileIcon() {
|
||||||
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor"><path d="M263.72-96Q234-96 213-117.15T192-168v-624q0-29.7 21.15-50.85Q234.3-864 264-864h312l192 192v504q0 29.7-21.16 50.85Q725.68-96 695.96-96H263.72ZM528-624h168L528-792v168Z"/></svg>`;
|
return `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-file-earmark" viewBox="0 0 16 16">
|
||||||
|
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5z"/>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,8 @@ function setupFileInput(chooser) {
|
|||||||
inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.dragAndDropPDF;
|
inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.dragAndDropPDF;
|
||||||
} else if (inputContainer.id === 'image-upload-input-container') {
|
} else if (inputContainer.id === 'image-upload-input-container') {
|
||||||
inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.dragAndDropImage;
|
inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.dragAndDropImage;
|
||||||
|
} else if (inputContainer.id === 'attachments-input-container') {
|
||||||
|
inputContainer.querySelector('#dragAndDrop').innerHTML = window.fileInput.addAttachments;
|
||||||
}
|
}
|
||||||
let allFiles = [];
|
let allFiles = [];
|
||||||
let overlay;
|
let overlay;
|
||||||
|
@ -268,6 +268,7 @@
|
|||||||
window.fileInput = {
|
window.fileInput = {
|
||||||
dragAndDropPDF: '[[#{fileChooser.dragAndDropPDF}]]',
|
dragAndDropPDF: '[[#{fileChooser.dragAndDropPDF}]]',
|
||||||
dragAndDropImage: '[[#{fileChooser.dragAndDropImage}]]',
|
dragAndDropImage: '[[#{fileChooser.dragAndDropImage}]]',
|
||||||
|
addAttachments: '[[#{fileChooser.addAttachments}]]',
|
||||||
extractPDF: '[[#{fileChooser.extractPDF}]]',
|
extractPDF: '[[#{fileChooser.extractPDF}]]',
|
||||||
loading: '[[#{loading}]]'
|
loading: '[[#{loading}]]'
|
||||||
};</script>
|
};</script>
|
||||||
|
@ -105,6 +105,9 @@
|
|||||||
<div
|
<div
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('img-to-pdf', 'picture_as_pdf', 'home.imageToPdf.title', 'home.imageToPdf.desc', 'imageToPdf.tags', 'convertto')}">
|
th:replace="~{fragments/navbarEntry :: navbarEntry('img-to-pdf', 'picture_as_pdf', 'home.imageToPdf.title', 'home.imageToPdf.desc', 'imageToPdf.tags', 'convertto')}">
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
th:replace="~{fragments/navbarEntry :: navbarEntry('eml-to-pdf', 'email', 'home.EMLToPDF.title', 'home.EMLToPDF.desc', 'EMLToPDF.tags', 'convertto')}">
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
th:replace="~{fragments/navbarEntry :: navbarEntry('file-to-pdf', 'draft', 'home.fileToPDF.title', 'home.fileToPDF.desc', 'fileToPDF.tags', 'convertto')}">
|
th:replace="~{fragments/navbarEntry :: navbarEntry('file-to-pdf', 'draft', 'home.fileToPDF.title', 'home.fileToPDF.desc', 'fileToPDF.tags', 'convertto')}">
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.service.AttachmentServiceInterface;
|
||||||
|
import stirling.software.common.service.CustomPDFDocumentFactory;
|
||||||
|
import stirling.software.common.util.WebResponseUtils;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AttachmentControllerTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private AttachmentServiceInterface pdfAttachmentService;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private AttachmentController attachmentController;
|
||||||
|
|
||||||
|
private MockMultipartFile pdfFile;
|
||||||
|
private MockMultipartFile attachment1;
|
||||||
|
private MockMultipartFile attachment2;
|
||||||
|
private PDDocument mockDocument;
|
||||||
|
private PDDocument modifiedMockDocument;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
pdfFile = new MockMultipartFile("fileInput", "test.pdf", "application/pdf", "PDF content".getBytes());
|
||||||
|
attachment1 = new MockMultipartFile("attachment1", "file1.txt", "text/plain", "File 1 content".getBytes());
|
||||||
|
attachment2 = new MockMultipartFile("attachment2", "file2.jpg", "image/jpeg", "Image content".getBytes());
|
||||||
|
|
||||||
|
mockDocument = mock(PDDocument.class);
|
||||||
|
modifiedMockDocument = mock(PDDocument.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addAttachments_Success() throws IOException {
|
||||||
|
List<MultipartFile> attachments = List.of(attachment1, attachment2);
|
||||||
|
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("modified PDF content".getBytes());
|
||||||
|
|
||||||
|
when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument);
|
||||||
|
when(pdfAttachmentService.addAttachment(mockDocument, attachments)).thenReturn(modifiedMockDocument);
|
||||||
|
|
||||||
|
try (MockedStatic<WebResponseUtils> mockedWebResponseUtils = mockStatic(WebResponseUtils.class)) {
|
||||||
|
mockedWebResponseUtils.when(() -> WebResponseUtils.pdfDocToWebResponse(eq(modifiedMockDocument), eq("test_with_attachments.pdf")))
|
||||||
|
.thenReturn(expectedResponse);
|
||||||
|
|
||||||
|
ResponseEntity<byte[]> response = attachmentController.addAttachments(pdfFile, attachments);
|
||||||
|
|
||||||
|
assertNotNull(response);
|
||||||
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
assertNotNull(response.getBody());
|
||||||
|
verify(pdfDocumentFactory).load(pdfFile, false);
|
||||||
|
verify(pdfAttachmentService).addAttachment(mockDocument, attachments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addAttachments_SingleAttachment() throws IOException {
|
||||||
|
List<MultipartFile> attachments = List.of(attachment1);
|
||||||
|
ResponseEntity<byte[]> expectedResponse = ResponseEntity.ok("modified PDF content".getBytes());
|
||||||
|
|
||||||
|
when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument);
|
||||||
|
when(pdfAttachmentService.addAttachment(mockDocument, attachments)).thenReturn(modifiedMockDocument);
|
||||||
|
|
||||||
|
try (MockedStatic<WebResponseUtils> mockedWebResponseUtils = mockStatic(WebResponseUtils.class)) {
|
||||||
|
mockedWebResponseUtils.when(() -> WebResponseUtils.pdfDocToWebResponse(eq(modifiedMockDocument), eq("test_with_attachments.pdf")))
|
||||||
|
.thenReturn(expectedResponse);
|
||||||
|
|
||||||
|
ResponseEntity<byte[]> response = attachmentController.addAttachments(pdfFile, attachments);
|
||||||
|
|
||||||
|
assertNotNull(response);
|
||||||
|
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||||
|
assertNotNull(response.getBody());
|
||||||
|
verify(pdfDocumentFactory).load(pdfFile, false);
|
||||||
|
verify(pdfAttachmentService).addAttachment(mockDocument, attachments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addAttachments_IOExceptionFromPDFLoad() throws IOException {
|
||||||
|
List<MultipartFile> attachments = List.of(attachment1);
|
||||||
|
IOException ioException = new IOException("Failed to load PDF");
|
||||||
|
|
||||||
|
when(pdfDocumentFactory.load(pdfFile, false)).thenThrow(ioException);
|
||||||
|
|
||||||
|
assertThrows(IOException.class, () -> attachmentController.addAttachments(pdfFile, attachments));
|
||||||
|
verify(pdfDocumentFactory).load(pdfFile, false);
|
||||||
|
verifyNoInteractions(pdfAttachmentService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addAttachments_IOExceptionFromAttachmentService() throws IOException {
|
||||||
|
List<MultipartFile> attachments = List.of(attachment1);
|
||||||
|
IOException ioException = new IOException("Failed to add attachment");
|
||||||
|
|
||||||
|
when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument);
|
||||||
|
when(pdfAttachmentService.addAttachment(mockDocument, attachments)).thenThrow(ioException);
|
||||||
|
|
||||||
|
assertThrows(IOException.class, () -> attachmentController.addAttachments(pdfFile, attachments));
|
||||||
|
verify(pdfAttachmentService).addAttachment(mockDocument, attachments);
|
||||||
|
}
|
||||||
|
}
|
@ -1,221 +0,0 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
import static org.mockito.ArgumentMatchers.*;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
|
||||||
import org.apache.pdfbox.pdmodel.PageMode;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
|
||||||
import org.mockito.InjectMocks;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.mock.web.MockMultipartFile;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.service.PDFAttachmentServiceInterface;
|
|
||||||
import stirling.software.common.service.CustomPDFDocumentFactory;
|
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
|
||||||
class AttachmentsControllerTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private CustomPDFDocumentFactory pdfDocumentFactory;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private PDFAttachmentServiceInterface pdfAttachmentService;
|
|
||||||
|
|
||||||
@InjectMocks
|
|
||||||
private AttachmentsController attachmentsController;
|
|
||||||
|
|
||||||
private MockMultipartFile pdfFile;
|
|
||||||
private MockMultipartFile attachment1;
|
|
||||||
private MockMultipartFile attachment2;
|
|
||||||
private PDDocument mockDocument;
|
|
||||||
private PDDocumentCatalog mockCatalog;
|
|
||||||
private PDDocumentNameDictionary mockNameDict;
|
|
||||||
private PDEmbeddedFilesNameTreeNode mockEmbeddedFilesTree;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
pdfFile = new MockMultipartFile("fileInput", "test.pdf", "application/pdf", "PDF content".getBytes());
|
|
||||||
attachment1 = new MockMultipartFile("attachment1", "file1.txt", "text/plain", "File 1 content".getBytes());
|
|
||||||
attachment2 = new MockMultipartFile("attachment2", "file2.jpg", "image/jpeg", "Image content".getBytes());
|
|
||||||
|
|
||||||
mockDocument = mock(PDDocument.class);
|
|
||||||
mockCatalog = mock(PDDocumentCatalog.class);
|
|
||||||
mockNameDict = mock(PDDocumentNameDictionary.class);
|
|
||||||
mockEmbeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachments_WithExistingNames() throws IOException {
|
|
||||||
List<MultipartFile> attachments = List.of(attachment1, attachment2);
|
|
||||||
byte[] expectedOutput = "modified PDF content".getBytes();
|
|
||||||
|
|
||||||
when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument);
|
|
||||||
when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog);
|
|
||||||
when(mockCatalog.getNames()).thenReturn(mockNameDict);
|
|
||||||
when(mockNameDict.getEmbeddedFiles()).thenReturn(mockEmbeddedFilesTree);
|
|
||||||
when(pdfAttachmentService.addAttachment(mockDocument, mockEmbeddedFilesTree, attachments)).thenReturn(expectedOutput);
|
|
||||||
|
|
||||||
ResponseEntity<byte[]> response = attachmentsController.addAttachments(pdfFile, attachments);
|
|
||||||
|
|
||||||
assertNotNull(response);
|
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
|
||||||
assertNotNull(response.getBody());
|
|
||||||
verify(pdfDocumentFactory).load(pdfFile, false);
|
|
||||||
verify(mockCatalog).setNames(mockNameDict);
|
|
||||||
verify(pdfAttachmentService).addAttachment(mockDocument, mockEmbeddedFilesTree, attachments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachments_WithoutExistingNames() throws IOException {
|
|
||||||
List<MultipartFile> attachments = List.of(attachment1);
|
|
||||||
byte[] expectedOutput = "modified PDF content".getBytes();
|
|
||||||
|
|
||||||
try (PDDocument realDocument = new PDDocument()) {
|
|
||||||
when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(realDocument);
|
|
||||||
when(pdfAttachmentService.addAttachment(eq(realDocument), any(PDEmbeddedFilesNameTreeNode.class), eq(attachments))).thenReturn(expectedOutput);
|
|
||||||
|
|
||||||
ResponseEntity<byte[]> response = attachmentsController.addAttachments(pdfFile, attachments);
|
|
||||||
|
|
||||||
assertNotNull(response);
|
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
|
||||||
assertNotNull(response.getBody());
|
|
||||||
verify(pdfDocumentFactory).load(pdfFile, false);
|
|
||||||
verify(pdfAttachmentService).addAttachment(eq(realDocument), any(PDEmbeddedFilesNameTreeNode.class), eq(attachments));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachments_IOExceptionFromPDFLoad() throws IOException {
|
|
||||||
List<MultipartFile> attachments = List.of(attachment1);
|
|
||||||
IOException ioException = new IOException("Failed to load PDF");
|
|
||||||
|
|
||||||
when(pdfDocumentFactory.load(pdfFile, false)).thenThrow(ioException);
|
|
||||||
|
|
||||||
assertThrows(IOException.class, () -> attachmentsController.addAttachments(pdfFile, attachments));
|
|
||||||
verify(pdfDocumentFactory).load(pdfFile, false);
|
|
||||||
verifyNoInteractions(pdfAttachmentService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachments_IOExceptionFromAttachmentService() throws IOException {
|
|
||||||
List<MultipartFile> attachments = List.of(attachment1);
|
|
||||||
IOException ioException = new IOException("Failed to add attachment");
|
|
||||||
|
|
||||||
when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument);
|
|
||||||
when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog);
|
|
||||||
when(mockCatalog.getNames()).thenReturn(mockNameDict);
|
|
||||||
when(mockNameDict.getEmbeddedFiles()).thenReturn(mockEmbeddedFilesTree);
|
|
||||||
when(pdfAttachmentService.addAttachment(mockDocument, mockEmbeddedFilesTree, attachments)).thenThrow(ioException);
|
|
||||||
|
|
||||||
assertThrows(IOException.class, () -> attachmentsController.addAttachments(pdfFile, attachments));
|
|
||||||
verify(pdfAttachmentService).addAttachment(mockDocument, mockEmbeddedFilesTree, attachments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void removeAttachments_WithExistingNames() throws IOException {
|
|
||||||
when(pdfDocumentFactory.load(pdfFile)).thenReturn(mockDocument);
|
|
||||||
when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog);
|
|
||||||
when(mockCatalog.getNames()).thenReturn(mockNameDict);
|
|
||||||
|
|
||||||
ResponseEntity<byte[]> response = attachmentsController.removeAttachments(pdfFile);
|
|
||||||
|
|
||||||
assertNotNull(response);
|
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
|
||||||
verify(pdfDocumentFactory).load(pdfFile);
|
|
||||||
verify(mockNameDict).setEmbeddedFiles(null);
|
|
||||||
verify(mockCatalog).setPageMode(PageMode.USE_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void removeAttachments_WithoutExistingNames() throws IOException {
|
|
||||||
when(pdfDocumentFactory.load(pdfFile)).thenReturn(mockDocument);
|
|
||||||
when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog);
|
|
||||||
when(mockCatalog.getNames()).thenReturn(null);
|
|
||||||
|
|
||||||
ResponseEntity<byte[]> response = attachmentsController.removeAttachments(pdfFile);
|
|
||||||
|
|
||||||
assertNotNull(response);
|
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
|
||||||
verify(pdfDocumentFactory).load(pdfFile);
|
|
||||||
verify(mockCatalog).setPageMode(PageMode.USE_NONE);
|
|
||||||
verifyNoInteractions(mockNameDict);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void removeAttachments_IOExceptionFromPDFLoad() throws IOException {
|
|
||||||
IOException ioException = new IOException("Failed to load PDF");
|
|
||||||
|
|
||||||
when(pdfDocumentFactory.load(pdfFile)).thenThrow(ioException);
|
|
||||||
|
|
||||||
assertThrows(IOException.class, () -> attachmentsController.removeAttachments(pdfFile));
|
|
||||||
verify(pdfDocumentFactory).load(pdfFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachments_EmptyAttachmentsList() throws IOException {
|
|
||||||
List<MultipartFile> emptyAttachments = List.of();
|
|
||||||
byte[] expectedOutput = "PDF content without new attachments".getBytes();
|
|
||||||
|
|
||||||
when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument);
|
|
||||||
when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog);
|
|
||||||
when(mockCatalog.getNames()).thenReturn(mockNameDict);
|
|
||||||
when(mockNameDict.getEmbeddedFiles()).thenReturn(mockEmbeddedFilesTree);
|
|
||||||
when(pdfAttachmentService.addAttachment(mockDocument, mockEmbeddedFilesTree, emptyAttachments)).thenReturn(expectedOutput);
|
|
||||||
|
|
||||||
ResponseEntity<byte[]> response = attachmentsController.addAttachments(pdfFile, emptyAttachments);
|
|
||||||
|
|
||||||
assertNotNull(response);
|
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
|
||||||
assertNotNull(response.getBody());
|
|
||||||
verify(pdfAttachmentService).addAttachment(mockDocument, mockEmbeddedFilesTree, emptyAttachments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachments_NullFilename() throws IOException {
|
|
||||||
MockMultipartFile attachmentWithNullName = new MockMultipartFile("attachment", null, "text/plain", "content".getBytes());
|
|
||||||
List<MultipartFile> attachments = List.of(attachmentWithNullName);
|
|
||||||
byte[] expectedOutput = "PDF with null filename attachment".getBytes();
|
|
||||||
|
|
||||||
when(pdfDocumentFactory.load(pdfFile, false)).thenReturn(mockDocument);
|
|
||||||
when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog);
|
|
||||||
when(mockCatalog.getNames()).thenReturn(mockNameDict);
|
|
||||||
when(mockNameDict.getEmbeddedFiles()).thenReturn(mockEmbeddedFilesTree);
|
|
||||||
when(pdfAttachmentService.addAttachment(mockDocument, mockEmbeddedFilesTree, attachments)).thenReturn(expectedOutput);
|
|
||||||
|
|
||||||
ResponseEntity<byte[]> response = attachmentsController.addAttachments(pdfFile, attachments);
|
|
||||||
|
|
||||||
assertNotNull(response);
|
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
|
||||||
assertNotNull(response.getBody());
|
|
||||||
verify(pdfAttachmentService).addAttachment(mockDocument, mockEmbeddedFilesTree, attachments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void removeAttachments_NullPDFFilename() throws IOException {
|
|
||||||
MockMultipartFile pdfWithNullName = new MockMultipartFile("fileInput", null, "application/pdf", "PDF content".getBytes());
|
|
||||||
|
|
||||||
when(pdfDocumentFactory.load(pdfWithNullName)).thenReturn(mockDocument);
|
|
||||||
when(mockDocument.getDocumentCatalog()).thenReturn(mockCatalog);
|
|
||||||
when(mockCatalog.getNames()).thenReturn(null);
|
|
||||||
|
|
||||||
ResponseEntity<byte[]> response = attachmentsController.removeAttachments(pdfWithNullName);
|
|
||||||
|
|
||||||
assertNotNull(response);
|
|
||||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
|
||||||
verify(mockCatalog).setPageMode(PageMode.USE_NONE);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,105 @@
|
|||||||
|
package stirling.software.SPDF.service;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class AttachmentServiceTest {
|
||||||
|
|
||||||
|
private AttachmentService attachmentService;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
attachmentService = new AttachmentService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addAttachmentToPDF() throws IOException {
|
||||||
|
try (var document = new PDDocument()) {
|
||||||
|
document.setDocumentId(100L);
|
||||||
|
var attachments = List.of(mock(MultipartFile.class));
|
||||||
|
|
||||||
|
when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt");
|
||||||
|
when(attachments.get(0).getInputStream()).thenReturn(
|
||||||
|
new ByteArrayInputStream("Test content".getBytes()));
|
||||||
|
when(attachments.get(0).getSize()).thenReturn(12L);
|
||||||
|
when(attachments.get(0).getContentType()).thenReturn("text/plain");
|
||||||
|
|
||||||
|
PDDocument result = attachmentService.addAttachment(document, attachments);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(document.getDocumentId(), result.getDocumentId());
|
||||||
|
assertNotNull(result.getDocumentCatalog().getNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addAttachmentToPDF_MultipleAttachments() throws IOException {
|
||||||
|
try (var document = new PDDocument()) {
|
||||||
|
document.setDocumentId(100L);
|
||||||
|
var attachment1 = mock(MultipartFile.class);
|
||||||
|
var attachment2 = mock(MultipartFile.class);
|
||||||
|
var attachments = List.of(attachment1, attachment2);
|
||||||
|
|
||||||
|
when(attachment1.getOriginalFilename()).thenReturn("document.pdf");
|
||||||
|
when(attachment1.getInputStream()).thenReturn(
|
||||||
|
new ByteArrayInputStream("PDF content".getBytes()));
|
||||||
|
when(attachment1.getSize()).thenReturn(15L);
|
||||||
|
when(attachment1.getContentType()).thenReturn("application/pdf");
|
||||||
|
|
||||||
|
when(attachment2.getOriginalFilename()).thenReturn("image.jpg");
|
||||||
|
when(attachment2.getInputStream()).thenReturn(
|
||||||
|
new ByteArrayInputStream("Image content".getBytes()));
|
||||||
|
when(attachment2.getSize()).thenReturn(20L);
|
||||||
|
when(attachment2.getContentType()).thenReturn("image/jpeg");
|
||||||
|
|
||||||
|
PDDocument result = attachmentService.addAttachment(document, attachments);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertNotNull(result.getDocumentCatalog().getNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addAttachmentToPDF_WithBlankContentType() throws IOException {
|
||||||
|
try (var document = new PDDocument()) {
|
||||||
|
document.setDocumentId(100L);
|
||||||
|
var attachments = List.of(mock(MultipartFile.class));
|
||||||
|
|
||||||
|
when(attachments.get(0).getOriginalFilename()).thenReturn("image.jpg");
|
||||||
|
when(attachments.get(0).getInputStream()).thenReturn(
|
||||||
|
new ByteArrayInputStream("Image content".getBytes()));
|
||||||
|
when(attachments.get(0).getSize()).thenReturn(25L);
|
||||||
|
when(attachments.get(0).getContentType()).thenReturn("");
|
||||||
|
|
||||||
|
PDDocument result = attachmentService.addAttachment(document, attachments);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertNotNull(result.getDocumentCatalog().getNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addAttachmentToPDF_AttachmentInputStreamThrowsIOException() throws IOException {
|
||||||
|
try (var document = new PDDocument()) {
|
||||||
|
var attachments = List.of(mock(MultipartFile.class));
|
||||||
|
var ioException = new IOException("Failed to read attachment stream");
|
||||||
|
|
||||||
|
when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt");
|
||||||
|
when(attachments.get(0).getInputStream()).thenThrow(ioException);
|
||||||
|
when(attachments.get(0).getSize()).thenReturn(10L);
|
||||||
|
|
||||||
|
PDDocument result = attachmentService.addAttachment(document, attachments);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertNotNull(result.getDocumentCatalog().getNames());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,216 +0,0 @@
|
|||||||
package stirling.software.SPDF.service;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
|
||||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
|
||||||
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
|
|
||||||
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyMap;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
class PDFAttachmentServiceTest {
|
|
||||||
|
|
||||||
private PDFAttachmentService pdfAttachmentService;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
pdfAttachmentService = new PDFAttachmentService();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachmentToPDF() throws IOException {
|
|
||||||
try (var document = new PDDocument()) {
|
|
||||||
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
|
|
||||||
var attachments = List.of(mock(MultipartFile.class));
|
|
||||||
var existingNames = new HashMap<String, PDComplexFileSpecification>();
|
|
||||||
|
|
||||||
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
|
|
||||||
when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt");
|
|
||||||
when(attachments.get(0).getInputStream()).thenReturn(
|
|
||||||
new ByteArrayInputStream("Test content".getBytes()));
|
|
||||||
when(attachments.get(0).getSize()).thenReturn(12L);
|
|
||||||
when(attachments.get(0).getContentType()).thenReturn("text/plain");
|
|
||||||
|
|
||||||
byte[] result = pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
|
|
||||||
|
|
||||||
assertNotNull(result);
|
|
||||||
assertTrue(result.length > 0);
|
|
||||||
verify(embeddedFilesTree).setNames(anyMap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachmentToPDF_WithNullExistingNames() throws IOException {
|
|
||||||
try (var document = new PDDocument()) {
|
|
||||||
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
|
|
||||||
var attachments = List.of(mock(MultipartFile.class));
|
|
||||||
|
|
||||||
when(embeddedFilesTree.getNames()).thenReturn(null);
|
|
||||||
when(attachments.get(0).getOriginalFilename()).thenReturn("document.pdf");
|
|
||||||
when(attachments.get(0).getInputStream()).thenReturn(
|
|
||||||
new ByteArrayInputStream("PDF content".getBytes()));
|
|
||||||
when(attachments.get(0).getSize()).thenReturn(15L);
|
|
||||||
when(attachments.get(0).getContentType()).thenReturn("application/pdf");
|
|
||||||
|
|
||||||
byte[] result = pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
|
|
||||||
|
|
||||||
assertNotNull(result);
|
|
||||||
assertTrue(result.length > 0);
|
|
||||||
verify(embeddedFilesTree).setNames(anyMap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachmentToPDF_WithBlankContentType() throws IOException {
|
|
||||||
try (var document = new PDDocument()) {
|
|
||||||
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
|
|
||||||
var attachments = List.of(mock(MultipartFile.class));
|
|
||||||
var existingNames = new HashMap<String, PDComplexFileSpecification>();
|
|
||||||
|
|
||||||
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
|
|
||||||
when(attachments.get(0).getOriginalFilename()).thenReturn("image.jpg");
|
|
||||||
when(attachments.get(0).getInputStream()).thenReturn(
|
|
||||||
new ByteArrayInputStream("Image content".getBytes()));
|
|
||||||
when(attachments.get(0).getSize()).thenReturn(25L);
|
|
||||||
when(attachments.get(0).getContentType()).thenReturn("");
|
|
||||||
|
|
||||||
byte[] result = pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
|
|
||||||
|
|
||||||
assertNotNull(result);
|
|
||||||
assertTrue(result.length > 0);
|
|
||||||
verify(embeddedFilesTree).setNames(anyMap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachmentToPDF_GetNamesThrowsIOException() throws IOException {
|
|
||||||
var document = mock(PDDocument.class);
|
|
||||||
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
|
|
||||||
var attachments = List.of(mock(MultipartFile.class));
|
|
||||||
var ioException = new IOException("Failed to retrieve embedded files");
|
|
||||||
|
|
||||||
when(embeddedFilesTree.getNames()).thenThrow(ioException);
|
|
||||||
|
|
||||||
assertThrows(IOException.class, () -> pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments));
|
|
||||||
|
|
||||||
verify(embeddedFilesTree).getNames();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachmentToPDF_AttachmentInputStreamThrowsIOException() throws IOException {
|
|
||||||
try (var document = new PDDocument()) {
|
|
||||||
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
|
|
||||||
var attachments = List.of(mock(MultipartFile.class));
|
|
||||||
var existingNames = new HashMap<String, PDComplexFileSpecification>();
|
|
||||||
var ioException = new IOException("Failed to read attachment stream");
|
|
||||||
|
|
||||||
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
|
|
||||||
when(attachments.get(0).getOriginalFilename()).thenReturn("corrupted.file");
|
|
||||||
when(attachments.get(0).getInputStream()).thenThrow(ioException);
|
|
||||||
when(attachments.get(0).getSize()).thenReturn(10L);
|
|
||||||
|
|
||||||
byte[] result = pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
|
|
||||||
|
|
||||||
assertNotNull(result);
|
|
||||||
assertTrue(result.length > 0);
|
|
||||||
verify(embeddedFilesTree).setNames(anyMap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachmentToPDF_WithProtectedDocument() throws IOException {
|
|
||||||
try (var document = new PDDocument()) {
|
|
||||||
// Create a document with restricted permissions (this simulates an encrypted/protected document)
|
|
||||||
AccessPermission ap = new AccessPermission();
|
|
||||||
ap.setCanExtractContent(false); // Restrict content extraction initially
|
|
||||||
var spp = new StandardProtectionPolicy("owner", "user", ap);
|
|
||||||
document.protect(spp);
|
|
||||||
|
|
||||||
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
|
|
||||||
var attachments = List.of(mock(MultipartFile.class));
|
|
||||||
var existingNames = new HashMap<String, PDComplexFileSpecification>();
|
|
||||||
|
|
||||||
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
|
|
||||||
when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt");
|
|
||||||
when(attachments.get(0).getInputStream()).thenReturn(
|
|
||||||
new ByteArrayInputStream("Test content".getBytes()));
|
|
||||||
when(attachments.get(0).getSize()).thenReturn(12L);
|
|
||||||
when(attachments.get(0).getContentType()).thenReturn("text/plain");
|
|
||||||
|
|
||||||
byte[] result = pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
|
|
||||||
|
|
||||||
assertNotNull(result);
|
|
||||||
assertTrue(result.length > 0);
|
|
||||||
verify(embeddedFilesTree).setNames(anyMap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachmentToPDF_WithRestrictedPermissions() throws IOException {
|
|
||||||
try (var document = new PDDocument()) {
|
|
||||||
// Create a document with very restricted permissions that should block permission changes
|
|
||||||
AccessPermission ap = new AccessPermission();
|
|
||||||
ap.setCanModify(false);
|
|
||||||
ap.setCanAssembleDocument(false);
|
|
||||||
ap.setCanExtractContent(false);
|
|
||||||
var spp = new StandardProtectionPolicy("owner", "user", ap);
|
|
||||||
document.protect(spp);
|
|
||||||
|
|
||||||
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
|
|
||||||
var attachments = List.of(mock(MultipartFile.class));
|
|
||||||
var existingNames = new HashMap<String, PDComplexFileSpecification>();
|
|
||||||
|
|
||||||
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
|
|
||||||
when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt");
|
|
||||||
when(attachments.get(0).getInputStream()).thenReturn(
|
|
||||||
new ByteArrayInputStream("Test content".getBytes()));
|
|
||||||
when(attachments.get(0).getSize()).thenReturn(12L);
|
|
||||||
when(attachments.get(0).getContentType()).thenReturn("text/plain");
|
|
||||||
|
|
||||||
byte[] result = pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
|
|
||||||
|
|
||||||
assertNotNull(result);
|
|
||||||
assertTrue(result.length > 0);
|
|
||||||
verify(embeddedFilesTree).setNames(anyMap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void addAttachmentToPDF_WithNonEncryptedDocument() throws IOException {
|
|
||||||
try (var document = new PDDocument()) {
|
|
||||||
var embeddedFilesTree = mock(PDEmbeddedFilesNameTreeNode.class);
|
|
||||||
var attachments = List.of(mock(MultipartFile.class));
|
|
||||||
var existingNames = new HashMap<String, PDComplexFileSpecification>();
|
|
||||||
|
|
||||||
when(embeddedFilesTree.getNames()).thenReturn(existingNames);
|
|
||||||
when(attachments.get(0).getOriginalFilename()).thenReturn("test.txt");
|
|
||||||
when(attachments.get(0).getInputStream()).thenReturn(
|
|
||||||
new ByteArrayInputStream("Test content".getBytes()));
|
|
||||||
when(attachments.get(0).getSize()).thenReturn(12L);
|
|
||||||
when(attachments.get(0).getContentType()).thenReturn("text/plain");
|
|
||||||
|
|
||||||
byte[] result = pdfAttachmentService.addAttachment(document, embeddedFilesTree, attachments);
|
|
||||||
|
|
||||||
assertNotNull(result);
|
|
||||||
assertTrue(result.length > 0);
|
|
||||||
// Verify permissions are set correctly for non-encrypted documents
|
|
||||||
AccessPermission permissions = document.getCurrentAccessPermission();
|
|
||||||
assertTrue(permissions.canExtractContent());
|
|
||||||
assertTrue(permissions.canExtractForAccessibility());
|
|
||||||
assertTrue(permissions.canModifyAnnotations());
|
|
||||||
verify(embeddedFilesTree).setNames(anyMap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user