2023-12-11 23:20:31 +00:00
package stirling.software.SPDF.controller.api ;
2023-12-16 19:30:47 +00:00
import java.io.ByteArrayOutputStream ;
import java.io.File ;
import java.io.IOException ;
import java.util.HashMap ;
2023-12-29 22:53:46 +00:00
import java.util.List ;
2023-12-16 19:30:47 +00:00
import java.util.Map ;
2023-12-29 22:53:46 +00:00
import java.util.ArrayList ;
2023-12-11 23:20:31 +00:00
import org.apache.pdfbox.multipdf.Overlay ;
import org.apache.pdfbox.pdmodel.PDDocument ;
2023-12-16 19:30:47 +00:00
import org.springframework.http.MediaType ;
import org.springframework.http.ResponseEntity ;
import org.springframework.web.bind.annotation.ModelAttribute ;
2023-12-11 23:20:31 +00:00
import org.springframework.web.bind.annotation.PostMapping ;
import org.springframework.web.bind.annotation.RequestMapping ;
import org.springframework.web.bind.annotation.RestController ;
import org.springframework.web.multipart.MultipartFile ;
2023-12-16 19:30:47 +00:00
2023-12-11 23:20:31 +00:00
import io.swagger.v3.oas.annotations.Operation ;
import io.swagger.v3.oas.annotations.tags.Tag ;
import stirling.software.SPDF.model.api.general.OverlayPdfsRequest ;
import stirling.software.SPDF.utils.GeneralUtils ;
import stirling.software.SPDF.utils.WebResponseUtils ;
@RestController
@RequestMapping ( "/api/v1/general" )
@Tag ( name = "General" , description = "General APIs" )
public class PdfOverlayController {
2023-12-29 22:53:46 +00:00
@PostMapping ( value = "/overlay-pdfs" , consumes = "multipart/form-data" )
2023-12-11 23:20:31 +00:00
@Operation ( summary = "Overlay PDF files in various modes" , description = "Overlay PDF files onto a base PDF with different modes: Sequential, Interleaved, or Fixed Repeat. Input:PDF Output:PDF Type:MIMO" )
public ResponseEntity < byte []> overlayPdfs ( @ModelAttribute OverlayPdfsRequest request ) throws IOException {
MultipartFile baseFile = request . getFileInput ();
int overlayPos = request . getOverlayPosition ();
MultipartFile [] overlayFiles = request . getOverlayFiles ();
File [] overlayPdfFiles = new File [ overlayFiles . length ] ;
2023-12-29 22:53:46 +00:00
List < File > tempFiles = new ArrayList <> (); // List to keep track of temporary files
try {
for ( int i = 0 ; i < overlayFiles . length ; i ++ ) {
overlayPdfFiles [ i ] = GeneralUtils . multipartToFile ( overlayFiles [ i ] );
}
String mode = request . getOverlayMode (); // "SequentialOverlay", "InterleavedOverlay", "FixedRepeatOverlay"
int [] counts = request . getCounts (); // Used for FixedRepeatOverlay mode
try ( PDDocument basePdf = PDDocument . load ( baseFile . getInputStream ());
Overlay overlay = new Overlay ()) {
Map < Integer , String > overlayGuide = prepareOverlayGuide ( basePdf . getNumberOfPages (), overlayPdfFiles , mode , counts , tempFiles );
overlay . setInputPDF ( basePdf );
if ( overlayPos == 0 ) {
overlay . setOverlayPosition ( Overlay . Position . FOREGROUND );
} else {
overlay . setOverlayPosition ( Overlay . Position . BACKGROUND );
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream ();
overlay . overlay ( overlayGuide ). save ( outputStream );
byte [] data = outputStream . toByteArray ();
String outputFilename = baseFile . getOriginalFilename (). replaceFirst ( "[.][^.]+$" , "" ) + "_overlayed.pdf" ; // Remove file extension and append .pdf
return WebResponseUtils . bytesToWebResponse ( data , outputFilename , MediaType . APPLICATION_PDF );
}
2023-12-11 23:20:31 +00:00
} finally {
for ( File overlayPdfFile : overlayPdfFiles ) {
2023-12-29 22:53:46 +00:00
if ( overlayPdfFile != null ) {
overlayPdfFile . delete ();
}
}
for ( File tempFile : tempFiles ) { // Delete temporary files
if ( tempFile != null ) {
tempFile . delete ();
}
2023-12-11 23:20:31 +00:00
}
}
}
2023-12-29 22:53:46 +00:00
private Map < Integer , String > prepareOverlayGuide ( int basePageCount , File [] overlayFiles , String mode , int [] counts , List < File > tempFiles ) throws IOException {
2023-12-11 23:20:31 +00:00
Map < Integer , String > overlayGuide = new HashMap <> ();
switch ( mode ) {
case "SequentialOverlay" :
2023-12-29 22:53:46 +00:00
sequentialOverlay ( overlayGuide , overlayFiles , basePageCount , tempFiles );
2023-12-11 23:20:31 +00:00
break ;
case "InterleavedOverlay" :
interleavedOverlay ( overlayGuide , overlayFiles , basePageCount );
break ;
case "FixedRepeatOverlay" :
fixedRepeatOverlay ( overlayGuide , overlayFiles , counts , basePageCount );
break ;
default :
throw new IllegalArgumentException ( "Invalid overlay mode" );
}
return overlayGuide ;
}
2023-12-29 22:53:46 +00:00
private void sequentialOverlay ( Map < Integer , String > overlayGuide , File [] overlayFiles , int basePageCount , List < File > tempFiles ) throws IOException {
int overlayFileIndex = 0 ;
int pageCountInCurrentOverlay = 0 ;
for ( int basePageIndex = 1 ; basePageIndex <= basePageCount ; basePageIndex ++ ) {
if ( pageCountInCurrentOverlay == 0 || pageCountInCurrentOverlay >= getNumberOfPages ( overlayFiles [ overlayFileIndex ] )) {
pageCountInCurrentOverlay = 0 ;
overlayFileIndex = ( overlayFileIndex + 1 ) % overlayFiles . length ;
}
2023-12-17 12:35:50 +00:00
2023-12-29 22:53:46 +00:00
try ( PDDocument overlayPdf = PDDocument . load ( overlayFiles [ overlayFileIndex ] )) {
PDDocument singlePageDocument = new PDDocument ();
singlePageDocument . addPage ( overlayPdf . getPage ( pageCountInCurrentOverlay ));
File tempFile = File . createTempFile ( "overlay-page-" , ".pdf" );
singlePageDocument . save ( tempFile );
singlePageDocument . close ();
overlayGuide . put ( basePageIndex , tempFile . getAbsolutePath ());
tempFiles . add ( tempFile ); // Keep track of the temporary file for cleanup
2023-12-11 23:20:31 +00:00
}
2023-12-29 22:53:46 +00:00
pageCountInCurrentOverlay ++ ;
}
}
private int getNumberOfPages ( File file ) throws IOException {
try ( PDDocument doc = PDDocument . load ( file )) {
return doc . getNumberOfPages ();
2023-12-11 23:20:31 +00:00
}
}
2023-12-17 12:35:50 +00:00
2023-12-29 22:53:46 +00:00
2023-12-11 23:20:31 +00:00
private void interleavedOverlay ( Map < Integer , String > overlayGuide , File [] overlayFiles , int basePageCount ) throws IOException {
2023-12-29 22:53:46 +00:00
for ( int basePageIndex = 1 ; basePageIndex <= basePageCount ; basePageIndex ++ ) {
File overlayFile = overlayFiles [ ( basePageIndex - 1 ) % overlayFiles . length ] ;
// Load the overlay document to check its page count
try ( PDDocument overlayPdf = PDDocument . load ( overlayFile )) {
int overlayPageCount = overlayPdf . getNumberOfPages ();
if (( basePageIndex - 1 ) % overlayPageCount < overlayPageCount ) {
overlayGuide . put ( basePageIndex , overlayFile . getAbsolutePath ());
}
}
2023-12-11 23:20:31 +00:00
}
}
2023-12-29 22:53:46 +00:00
2023-12-11 23:20:31 +00:00
private void fixedRepeatOverlay ( Map < Integer , String > overlayGuide , File [] overlayFiles , int [] counts , int basePageCount ) throws IOException {
if ( overlayFiles . length != counts . length ) {
throw new IllegalArgumentException ( "Counts array length must match the number of overlay files" );
}
int currentPage = 1 ;
for ( int i = 0 ; i < overlayFiles . length ; i ++ ) {
2023-12-29 22:53:46 +00:00
File overlayFile = overlayFiles [ i ] ;
2023-12-11 23:20:31 +00:00
int repeatCount = counts [ i ] ;
2023-12-29 22:53:46 +00:00
// Load the overlay document to check its page count
try ( PDDocument overlayPdf = PDDocument . load ( overlayFile )) {
int overlayPageCount = overlayPdf . getNumberOfPages ();
for ( int j = 0 ; j < repeatCount ; j ++ ) {
for ( int page = 0 ; page < overlayPageCount ; page ++ ) {
if ( currentPage > basePageCount ) break ;
overlayGuide . put ( currentPage ++ , overlayFile . getAbsolutePath ());
}
}
2023-12-11 23:20:31 +00:00
}
}
}
2023-12-29 22:53:46 +00:00
2023-12-11 23:20:31 +00:00
}
// Additional classes like OverlayPdfsRequest, WebResponseUtils, etc. are assumed to be defined elsewhere.