2023-05-31 21:33:45 +01:00
package stirling.software.SPDF.controller.api ;
2023-05-27 14:23:25 +01:00
2023-09-02 20:21:55 +01:00
2023-09-29 23:58:37 +01:00
import java.awt.Color ;
2023-05-27 14:23:25 +01:00
import java.io.ByteArrayOutputStream ;
import java.io.IOException ;
2023-09-03 01:24:02 +01:00
import org.apache.pdfbox.multipdf.LayerUtility ;
2023-09-02 20:21:55 +01:00
import org.apache.pdfbox.pdmodel.PDDocument ;
import org.apache.pdfbox.pdmodel.PDPage ;
import org.apache.pdfbox.pdmodel.PDPageContentStream ;
import org.apache.pdfbox.pdmodel.common.PDRectangle ;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject ;
2023-09-03 01:24:02 +01:00
import org.apache.pdfbox.util.Matrix ;
2023-05-27 14:23:25 +01:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
import org.springframework.http.ResponseEntity ;
2023-09-09 00:25:27 +01:00
import org.springframework.web.bind.annotation.ModelAttribute ;
2023-05-27 14:23:25 +01:00
import org.springframework.web.bind.annotation.PostMapping ;
2023-09-11 23:19:50 +01:00
import org.springframework.web.bind.annotation.RequestMapping ;
2023-05-27 14:23:25 +01:00
import org.springframework.web.bind.annotation.RestController ;
import org.springframework.web.multipart.MultipartFile ;
import io.swagger.v3.oas.annotations.Operation ;
2023-06-25 09:16:32 +01:00
import io.swagger.v3.oas.annotations.tags.Tag ;
2023-09-09 00:25:27 +01:00
import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest ;
2023-05-31 20:15:48 +01:00
import stirling.software.SPDF.utils.WebResponseUtils ;
2023-05-27 14:23:25 +01:00
@RestController
2023-09-11 23:19:50 +01:00
@RequestMapping ( " /api/v1/general " )
2023-06-25 09:16:32 +01:00
@Tag ( name = " General " , description = " General APIs " )
2023-05-27 14:23:25 +01:00
public class MultiPageLayoutController {
private static final Logger logger = LoggerFactory . getLogger ( MultiPageLayoutController . class ) ;
@PostMapping ( value = " /multi-page-layout " , consumes = " multipart/form-data " )
2023-09-02 20:21:55 +01:00
@Operation (
summary = " Merge multiple pages of a PDF document into a single page " ,
description = " This operation takes an input PDF file and the number of pages to merge into a single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO "
)
2023-09-09 00:25:27 +01:00
public ResponseEntity < byte [ ] > mergeMultiplePagesIntoOne ( @ModelAttribute MergeMultiplePagesRequest request )
2023-09-02 20:21:55 +01:00
throws IOException {
2023-09-13 00:46:30 +01:00
int pagesPerSheet = request . getPagesPerSheet ( ) ;
MultipartFile file = request . getFileInput ( ) ;
2023-09-29 23:58:37 +01:00
boolean addBorder = request . isAddBorder ( ) ;
2023-09-13 00:46:30 +01:00
if ( pagesPerSheet ! = 2 & & pagesPerSheet ! = 3 & & pagesPerSheet ! = ( int ) Math . sqrt ( pagesPerSheet ) * Math . sqrt ( pagesPerSheet ) ) {
throw new IllegalArgumentException ( " pagesPerSheet must be 2, 3 or a perfect square " ) ;
}
2023-09-02 20:21:55 +01:00
2023-09-13 00:46:30 +01:00
int cols = pagesPerSheet = = 2 | | pagesPerSheet = = 3 ? pagesPerSheet : ( int ) Math . sqrt ( pagesPerSheet ) ;
int rows = pagesPerSheet = = 2 | | pagesPerSheet = = 3 ? 1 : ( int ) Math . sqrt ( pagesPerSheet ) ;
2023-09-02 20:21:55 +01:00
2023-09-13 00:46:30 +01:00
PDDocument sourceDocument = PDDocument . load ( file . getInputStream ( ) ) ;
PDDocument newDocument = new PDDocument ( ) ;
PDPage newPage = new PDPage ( PDRectangle . A4 ) ;
newDocument . addPage ( newPage ) ;
2023-09-02 20:21:55 +01:00
2023-09-13 00:46:30 +01:00
int totalPages = sourceDocument . getNumberOfPages ( ) ;
float cellWidth = newPage . getMediaBox ( ) . getWidth ( ) / cols ;
float cellHeight = newPage . getMediaBox ( ) . getHeight ( ) / rows ;
2023-09-02 20:21:55 +01:00
2023-09-13 00:46:30 +01:00
PDPageContentStream contentStream = new PDPageContentStream ( newDocument , newPage , PDPageContentStream . AppendMode . APPEND , true , true ) ;
LayerUtility layerUtility = new LayerUtility ( newDocument ) ;
2023-09-02 20:21:55 +01:00
2023-09-29 23:58:37 +01:00
float borderThickness = 1 . 5f ; // Specify border thickness as required
contentStream . setLineWidth ( borderThickness ) ;
contentStream . setStrokingColor ( Color . BLACK ) ;
2023-09-13 00:46:30 +01:00
for ( int i = 0 ; i < totalPages ; i + + ) {
if ( i ! = 0 & & i % pagesPerSheet = = 0 ) {
// Close the current content stream and create a new page and content stream
contentStream . close ( ) ;
newPage = new PDPage ( PDRectangle . A4 ) ;
newDocument . addPage ( newPage ) ;
contentStream = new PDPageContentStream ( newDocument , newPage , PDPageContentStream . AppendMode . APPEND , true , true ) ;
}
2023-09-02 20:21:55 +01:00
2023-09-13 00:46:30 +01:00
PDPage sourcePage = sourceDocument . getPage ( i ) ;
PDRectangle rect = sourcePage . getMediaBox ( ) ;
float scaleWidth = cellWidth / rect . getWidth ( ) ;
float scaleHeight = cellHeight / rect . getHeight ( ) ;
float scale = Math . min ( scaleWidth , scaleHeight ) ;
2023-09-02 20:21:55 +01:00
2023-09-13 00:46:30 +01:00
int adjustedPageIndex = i % pagesPerSheet ; // This will reset the index for every new page
int rowIndex = adjustedPageIndex / cols ;
int colIndex = adjustedPageIndex % cols ;
2023-09-02 20:21:55 +01:00
2023-09-13 00:46:30 +01:00
float x = colIndex * cellWidth + ( cellWidth - rect . getWidth ( ) * scale ) / 2 ;
float y = newPage . getMediaBox ( ) . getHeight ( ) - ( ( rowIndex + 1 ) * cellHeight - ( cellHeight - rect . getHeight ( ) * scale ) / 2 ) ;
2023-09-02 20:21:55 +01:00
2023-09-13 00:46:30 +01:00
contentStream . saveGraphicsState ( ) ;
contentStream . transform ( Matrix . getTranslateInstance ( x , y ) ) ;
contentStream . transform ( Matrix . getScaleInstance ( scale , scale ) ) ;
2023-09-02 20:21:55 +01:00
2023-09-13 00:46:30 +01:00
PDFormXObject formXObject = layerUtility . importPageAsForm ( sourceDocument , i ) ;
contentStream . drawForm ( formXObject ) ;
2023-09-02 20:21:55 +01:00
2023-09-13 00:46:30 +01:00
contentStream . restoreGraphicsState ( ) ;
2023-09-29 23:58:37 +01:00
if ( addBorder ) {
// Draw border around each page
float borderX = colIndex * cellWidth ;
float borderY = newPage . getMediaBox ( ) . getHeight ( ) - ( rowIndex + 1 ) * cellHeight ;
contentStream . addRect ( borderX , borderY , cellWidth , cellHeight ) ;
contentStream . stroke ( ) ;
}
2023-09-13 00:46:30 +01:00
}
2023-09-02 20:21:55 +01:00
2023-09-13 00:46:30 +01:00
contentStream . close ( ) ; // Close the final content stream
sourceDocument . close ( ) ;
ByteArrayOutputStream baos = new ByteArrayOutputStream ( ) ;
newDocument . save ( baos ) ;
newDocument . close ( ) ;
2023-09-02 20:21:55 +01:00
byte [ ] result = baos . toByteArray ( ) ;
return WebResponseUtils . bytesToWebResponse ( result , file . getOriginalFilename ( ) . replaceFirst ( " [.][^.]+$ " , " " ) + " _layoutChanged.pdf " ) ;
2023-05-27 14:23:25 +01:00
}
2023-09-02 20:21:55 +01:00
2023-05-27 14:23:25 +01:00
}