mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2024-11-19 05:40:10 +01:00
commit
a43c296eef
@ -65,6 +65,7 @@ public class EndpointConfiguration {
|
||||
addEndpointToGroup("PageOps", "pdf-organizer");
|
||||
addEndpointToGroup("PageOps", "rotate-pdf");
|
||||
addEndpointToGroup("PageOps", "multi-page-layout");
|
||||
addEndpointToGroup("PageOps", "scale-pages");
|
||||
|
||||
// Adding endpoints to "Convert" group
|
||||
addEndpointToGroup("Convert", "pdf-to-img");
|
||||
|
@ -0,0 +1,88 @@
|
||||
package stirling.software.SPDF.controller.api.other;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.itextpdf.kernel.geom.PageSize;
|
||||
import com.itextpdf.kernel.geom.Rectangle;
|
||||
import com.itextpdf.kernel.pdf.PdfDocument;
|
||||
import com.itextpdf.kernel.pdf.PdfPage;
|
||||
import com.itextpdf.kernel.pdf.PdfReader;
|
||||
import com.itextpdf.kernel.pdf.PdfWriter;
|
||||
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
|
||||
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@RestController
|
||||
public class ScalePagesController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ScalePagesController.class);
|
||||
|
||||
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
||||
@Operation(summary = "Change the size of a PDF page/document", description = "This operation takes an input PDF file and the size to scale the pages to in the output PDF file.")
|
||||
public ResponseEntity<byte[]> mergeMultiplePagesIntoOne(
|
||||
@Parameter(description = "The input PDF file", required = true) @RequestParam("fileInput") MultipartFile file,
|
||||
@Parameter(description = "The scale of pages in the output PDF. Acceptable values are A4.", required = true, schema = @Schema(type = "String", allowableValues = { "A4" })) @RequestParam("pageSize") String targetPageSize,
|
||||
@Parameter(description = "The scale of the content on the pages of the output PDF. Acceptable values are floats.", required = true, schema = @Schema(type = "float")) @RequestParam("scaleFactor") float scaleFactor)
|
||||
throws IOException {
|
||||
|
||||
if (!targetPageSize.equals("A4")) {
|
||||
throw new IllegalArgumentException("pageSize must be A4");
|
||||
}
|
||||
|
||||
byte[] bytes = file.getBytes();
|
||||
PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes));
|
||||
PdfDocument pdfDoc = new PdfDocument(reader);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
PdfWriter writer = new PdfWriter(baos);
|
||||
PdfDocument outputPdf = new PdfDocument(writer);
|
||||
|
||||
PageSize pageSize = new PageSize(PageSize.A4); // TODO: This (and all other PageSize.A4) need to be dynamically changed in response to targetPageSize
|
||||
|
||||
int totalPages = pdfDoc.getNumberOfPages();
|
||||
|
||||
for (int i = 1; i <= totalPages; i++) {
|
||||
PdfPage page = outputPdf.addNewPage(pageSize);
|
||||
PdfCanvas pdfCanvas = new PdfCanvas(page);
|
||||
|
||||
// Get the page and calculate scaling factors
|
||||
Rectangle rect = pdfDoc.getPage(i).getPageSize();
|
||||
float scaleWidth = PageSize.A4.getWidth() / rect.getWidth();
|
||||
float scaleHeight = PageSize.A4.getHeight() / rect.getHeight();
|
||||
float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor;
|
||||
System.out.println("Scale: " + scale);
|
||||
|
||||
PdfFormXObject formXObject = pdfDoc.getPage(i).copyAsFormXObject(outputPdf);
|
||||
float x = (PageSize.A4.getWidth() - rect.getWidth() * scale) / 2; // Center Page
|
||||
float y = (PageSize.A4.getHeight() - rect.getHeight() * scale) / 2;
|
||||
|
||||
// Save the graphics state, apply the transformations, add the object, and then
|
||||
// restore the graphics state
|
||||
pdfCanvas.saveState();
|
||||
pdfCanvas.concatMatrix(scale, 0, 0, scale, x, y);
|
||||
pdfCanvas.addXObject(formXObject, 0, 0);
|
||||
pdfCanvas.restoreState();
|
||||
}
|
||||
|
||||
outputPdf.close();
|
||||
byte[] pdfContent = baos.toByteArray();
|
||||
pdfDoc.close();
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_modified.pdf\"")
|
||||
.body(pdfContent);
|
||||
}
|
||||
}
|
@ -115,6 +115,11 @@ public class OtherWebController {
|
||||
return "other/multi-page-layout";
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/scale-pages")
|
||||
@Hidden
|
||||
public String scalePagesFrom(Model model) {
|
||||
model.addAttribute("currentPage", "scale-pages");
|
||||
return "other/scale-pages";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -131,6 +131,9 @@ home.certSign.desc=Signs a PDF with a Certificate/Key (PEM/P12)
|
||||
home.pageLayout.title=Multi-Page Layout
|
||||
home.pageLayout.desc=Merge multiple pages of a PDF document into a single page
|
||||
|
||||
home.scalePages.title=Adjust page-scale
|
||||
home.scalePages.desc=Change the size of the pages of a PDF document
|
||||
|
||||
error.pdfPassword=The PDF Document is passworded and either the password was not provided or was incorrect
|
||||
|
||||
downloadPdf=Download PDF
|
||||
@ -144,6 +147,12 @@ pageLayout.header=Multi Page Layout
|
||||
pageLayout.pagesPerSheet=Pages per sheet:
|
||||
pageLayout.submit=Submit
|
||||
|
||||
scalePages.title=Adjust page-scale
|
||||
scalePages.header=Adjust page-scale
|
||||
scalePages.pageSize=Size of a page of the document.
|
||||
scalePages.scaleFactor=Zoom level (crop) of a page.
|
||||
scalePages.submit=Submit
|
||||
|
||||
certSign.title=Certificate Signing
|
||||
certSign.header=Sign a PDF with your certificate (Work in progress)
|
||||
certSign.selectPDF=Select a PDF File for Signing:
|
||||
|
3
src/main/resources/static/images/scale-pages.svg
Normal file
3
src/main/resources/static/images/scale-pages.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-grid" viewBox="0 0 16 16">
|
||||
<path d="M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 880 B |
@ -157,6 +157,7 @@ function compareVersions(version1, version2) {
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'rotate-pdf', 'images/arrow-clockwise.svg', 'home.rotate.title', 'home.rotate.desc')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'remove-pages', 'images/file-earmark-x.svg', 'home.removePages.title', 'home.removePages.desc')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'multi-page-layout', 'images/page-layout.svg', 'home.pageLayout.title', 'home.pageLayout.desc')}"></div>
|
||||
<div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'scale-pages', 'images/scale-pages.svg', 'home.scalePages.title', 'home.scalePages.desc')}"></div>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
|
@ -137,7 +137,8 @@ filter: invert(0.2) sepia(2) saturate(50) hue-rotate(190deg);
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='cert-sign', cardTitle=#{home.certSign.title}, cardText=#{home.certSign.desc}, cardLink='cert-sign', svgPath='images/award.svg')}"></div>
|
||||
<div th:replace="~{fragments/card :: card(id='multi-page-layout', cardTitle=#{home.pageLayout.title}, cardText=#{home.pageLayout.desc}, cardLink='multi-page-layout', svgPath='images/page-layout.svg')}"></div>
|
||||
|
||||
<div th:replace="~{fragments/card :: card(id='scale-pages', cardTitle=#{home.scalePages.title}, cardText=#{home.scalePages.desc}, cardLink='scale-pages', svgPath='images/scale-pages.svg')}"></div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
39
src/main/resources/templates/other/scale-pages.html
Normal file
39
src/main/resources/templates/other/scale-pages.html
Normal file
@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{pageLayout.title})}"></th:block>
|
||||
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
|
||||
<br> <br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<h2 th:text="#{scalePages.header}"></h2>
|
||||
<form id="scalePagesFrom" th:action="@{scale-pages}" method="post" enctype="multipart/form-data">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="form-group">
|
||||
<label for="pageSize" th:text="#{scalePages.pageSize}"></label>
|
||||
<select id="pageSize" name="pageSize" required>
|
||||
<option value="A4">A4</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="scaleFactor" th:text="#{scalePages.scaleFactor}"></label>
|
||||
<input type="number" id="scaleFactor" name="scaleFactor" step="any" min="0" value="1">
|
||||
</div>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{scalePages.submit}"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user