some more changes also broke pipeline a bit

This commit is contained in:
Anthony Stirling 2023-07-15 16:06:33 +01:00
parent 6e32c7fe85
commit 9af1b0cfdc
7 changed files with 430 additions and 313 deletions

View file

@ -21,7 +21,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
import io.swagger.v3.oas.annotations.media.Schema;
@RestController @RestController
@Tag(name = "Filter", description = "Filter APIs") @Tag(name = "Filter", description = "Filter APIs")
public class FilterController { public class FilterController {
@ -37,6 +37,7 @@ public class FilterController {
return PdfUtils.hasText(pdfDocument, pageNumber); return PdfUtils.hasText(pdfDocument, pageNumber);
} }
//TODO
@PostMapping(consumes = "multipart/form-data", value = "/contains-image") @PostMapping(consumes = "multipart/form-data", value = "/contains-image")
@Operation(summary = "Checks if a PDF contains an image", description = "Input:PDF Output:Boolean Type:SISO") @Operation(summary = "Checks if a PDF contains an image", description = "Input:PDF Output:Boolean Type:SISO")
public Boolean containsImage( public Boolean containsImage(
@ -52,7 +53,9 @@ public class FilterController {
public Boolean pageCount( public Boolean pageCount(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile, @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
@Parameter(description = "Page Count", required = true) String pageCount, @Parameter(description = "Page Count", required = true) String pageCount,
@Parameter(description = "Comparison type, accepts Greater, Equal, Less than", required = false) String comparator) @Parameter(description = "Comparison type",
schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than",
allowableValues = {"Greater", "Equal", "Less"})) String comparator)
throws IOException, InterruptedException { throws IOException, InterruptedException {
// Load the PDF // Load the PDF
PDDocument document = PDDocument.load(inputFile.getInputStream()); PDDocument document = PDDocument.load(inputFile.getInputStream());
@ -76,7 +79,9 @@ public class FilterController {
public Boolean pageSize( public Boolean pageSize(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile, @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
@Parameter(description = "Standard Page Size", required = true) String standardPageSize, @Parameter(description = "Standard Page Size", required = true) String standardPageSize,
@Parameter(description = "Comparison type, accepts Greater, Equal, Less than", required = false) String comparator) @Parameter(description = "Comparison type",
schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than",
allowableValues = {"Greater", "Equal", "Less"})) String comparator)
throws IOException, InterruptedException { throws IOException, InterruptedException {
// Load the PDF // Load the PDF
@ -111,7 +116,9 @@ public class FilterController {
public Boolean fileSize( public Boolean fileSize(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile, @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
@Parameter(description = "File Size", required = true) String fileSize, @Parameter(description = "File Size", required = true) String fileSize,
@Parameter(description = "Comparison type, accepts Greater, Equal, Less than", required = false) String comparator) @Parameter(description = "Comparison type",
schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than",
allowableValues = {"Greater", "Equal", "Less"})) String comparator)
throws IOException, InterruptedException { throws IOException, InterruptedException {
// Get the file size // Get the file size
@ -136,7 +143,9 @@ public class FilterController {
public Boolean pageRotation( public Boolean pageRotation(
@RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile, @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file", required = true) MultipartFile inputFile,
@Parameter(description = "Rotation in degrees", required = true) int rotation, @Parameter(description = "Rotation in degrees", required = true) int rotation,
@Parameter(description = "Comparison type, accepts Greater, Equal, Less than", required = false) String comparator) @Parameter(description = "Comparison type",
schema = @Schema(description = "The comparison type, accepts Greater, Equal, Less than",
allowableValues = {"Greater", "Equal", "Less"})) String comparator)
throws IOException, InterruptedException { throws IOException, InterruptedException {
// Load the PDF // Load the PDF

View file

@ -30,6 +30,8 @@ import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer; import com.google.zxing.common.HybridBinarizer;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@RestController @RestController
public class AutoSplitPdfController { public class AutoSplitPdfController {
@ -37,7 +39,10 @@ public class AutoSplitPdfController {
private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF"; private static final String QR_CONTENT = "https://github.com/Frooodle/Stirling-PDF";
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data") @PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
public ResponseEntity<byte[]> autoSplitPdf(@RequestParam("fileInput") MultipartFile file) throws IOException { @Operation(summary = "Auto split PDF pages into separate documents", description = "This endpoint accepts a PDF file, scans each page for a specific QR code, and splits the document at the QR code boundaries. The output is a zip file containing each separate PDF document. Input:PDF Output:ZIP Type:SISO")
public ResponseEntity<byte[]> autoSplitPdf(
@RequestParam("fileInput") @Parameter(description = "The input PDF file which needs to be split into separate documents based on QR code boundaries.", required = true) MultipartFile file)
throws IOException {
InputStream inputStream = file.getInputStream(); InputStream inputStream = file.getInputStream();
PDDocument document = PDDocument.load(inputStream); PDDocument document = PDDocument.load(inputStream);
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer pdfRenderer = new PDFRenderer(document);

View file

@ -53,9 +53,9 @@ import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@Tag(name = "Pipeline", description = "Pipeline APIs") @Tag(name = "Pipeline", description = "Pipeline APIs")
public class Controller { public class PipelineController {
private static final Logger logger = LoggerFactory.getLogger(Controller.class); private static final Logger logger = LoggerFactory.getLogger(PipelineController.class);
@Autowired @Autowired
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
@ -246,11 +246,11 @@ public class Controller {
List<Resource> processFiles(List<Resource> outputFiles, String jsonString) throws Exception { List<Resource> processFiles(List<Resource> outputFiles, String jsonString) throws Exception {
logger.info("Processing files... " + outputFiles);
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(jsonString); JsonNode jsonNode = mapper.readTree(jsonString);
JsonNode pipelineNode = jsonNode.get("pipeline"); JsonNode pipelineNode = jsonNode.get("pipeline");
logger.info("Running pipelineNode: {}", pipelineNode);
ByteArrayOutputStream logStream = new ByteArrayOutputStream(); ByteArrayOutputStream logStream = new ByteArrayOutputStream();
PrintStream logPrintStream = new PrintStream(logStream); PrintStream logPrintStream = new PrintStream(logStream);
@ -298,6 +298,19 @@ public class Controller {
continue; continue;
} }
// Define filename
String filename;
if ("auto-rename".equals(operation)) {
// If the operation is "auto-rename", generate a new filename.
// This is a simple example of generating a filename using current timestamp.
// Modify as per your needs.
filename = "file_" + System.currentTimeMillis();
} else {
// Otherwise, keep the original filename.
filename = file.getFilename();
}
// Check if the response body is a zip file // Check if the response body is a zip file
if (isZip(response.getBody())) { if (isZip(response.getBody())) {
// Unzip the file and add all the files to the new output files // Unzip the file and add all the files to the new output files
@ -306,7 +319,7 @@ public class Controller {
Resource outputResource = new ByteArrayResource(response.getBody()) { Resource outputResource = new ByteArrayResource(response.getBody()) {
@Override @Override
public String getFilename() { public String getFilename() {
return file.getFilename(); // Preserving original filename return filename;
} }
}; };
newOutputFiles.add(outputResource); newOutputFiles.add(outputResource);

View file

@ -47,6 +47,11 @@ public class WatermarkController {
@RequestPart(required = true) @Parameter(description = "The watermark type (text or image)") String watermarkType, @RequestPart(required = true) @Parameter(description = "The watermark type (text or image)") String watermarkType,
@RequestPart(required = false) @Parameter(description = "The watermark text") String watermarkText, @RequestPart(required = false) @Parameter(description = "The watermark text") String watermarkText,
@RequestPart(required = false) @Parameter(description = "The watermark image") MultipartFile watermarkImage, @RequestPart(required = false) @Parameter(description = "The watermark image") MultipartFile watermarkImage,
@RequestParam(defaultValue = "roman", name = "alphabet") @Parameter(description = "The selected alphabet",
schema = @Schema(type = "string",
allowableValues = {"roman","arabic","japanese","korean","chinese"},
defaultValue = "roman")) String alphabet,
@RequestParam(defaultValue = "30", name = "fontSize") @Parameter(description = "The font size of the watermark text", example = "30") float fontSize, @RequestParam(defaultValue = "30", name = "fontSize") @Parameter(description = "The font size of the watermark text", example = "30") float fontSize,
@RequestParam(defaultValue = "0", name = "rotation") @Parameter(description = "The rotation of the watermark in degrees", example = "0") float rotation, @RequestParam(defaultValue = "0", name = "rotation") @Parameter(description = "The rotation of the watermark in degrees", example = "0") float rotation,
@RequestParam(defaultValue = "0.5", name = "opacity") @Parameter(description = "The opacity of the watermark (0.0 - 1.0)", example = "0.5") float opacity, @RequestParam(defaultValue = "0.5", name = "opacity") @Parameter(description = "The opacity of the watermark (0.0 - 1.0)", example = "0.5") float opacity,
@ -71,7 +76,7 @@ public class WatermarkController {
if (watermarkType.equalsIgnoreCase("text")) { if (watermarkType.equalsIgnoreCase("text")) {
addTextWatermark(contentStream, watermarkText, document, page, rotation, widthSpacer, heightSpacer, addTextWatermark(contentStream, watermarkText, document, page, rotation, widthSpacer, heightSpacer,
fontSize); fontSize, alphabet);
} else if (watermarkType.equalsIgnoreCase("image")) { } else if (watermarkType.equalsIgnoreCase("image")) {
addImageWatermark(contentStream, watermarkImage, document, page, rotation, widthSpacer, heightSpacer, addImageWatermark(contentStream, watermarkImage, document, page, rotation, widthSpacer, heightSpacer,
fontSize); fontSize);
@ -86,9 +91,41 @@ public class WatermarkController {
} }
private void addTextWatermark(PDPageContentStream contentStream, String watermarkText, PDDocument document, private void addTextWatermark(PDPageContentStream contentStream, String watermarkText, PDDocument document,
PDPage page, float rotation, int widthSpacer, int heightSpacer, float fontSize) throws IOException { PDPage page, float rotation, int widthSpacer, int heightSpacer, float fontSize, String alphabet) throws IOException {
// Set font and other properties for text watermark String resourceDir = "";
PDFont font = PDType1Font.HELVETICA_BOLD; PDFont font = PDType1Font.HELVETICA_BOLD;
switch (alphabet) {
case "arabic":
resourceDir = "static/fonts/NotoSansArabic-Regular.ttf";
break;
case "japanese":
resourceDir = "static/fonts/Meiryo.ttf";
break;
case "korean":
resourceDir = "static/fonts/malgun.ttf";
break;
case "chinese":
resourceDir = "static/fonts/SimSun.ttf";
break;
case "roman":
default:
resourceDir = "static/fonts/NotoSans-Regular.ttf";
break;
}
if(!resourceDir.equals("")) {
ClassPathResource classPathResource = new ClassPathResource(resourceDir);
String fileExtension = resourceDir.substring(resourceDir.lastIndexOf("."));
File tempFile = File.createTempFile("NotoSansFont", fileExtension);
try (InputStream is = classPathResource.getInputStream(); FileOutputStream os = new FileOutputStream(tempFile)) {
IOUtils.copy(is, os);
}
font = PDType0Font.load(document, tempFile);
tempFile.deleteOnExit();
}
contentStream.setFont(font, fontSize); contentStream.setFont(font, fontSize);
contentStream.setNonStrokingColor(Color.LIGHT_GRAY); contentStream.setNonStrokingColor(Color.LIGHT_GRAY);

View file

@ -44,7 +44,7 @@ public class PdfUtils {
public static PDRectangle textToPageSize(String size) { public static PDRectangle textToPageSize(String size) {
switch (size) { switch (size.toUpperCase()) {
case "A0": case "A0":
return PDRectangle.A0; return PDRectangle.A0;
case "A1": case "A1":

View file

@ -87,7 +87,7 @@ document.getElementById('submitConfigBtn').addEventListener('click', function()
let formData = new FormData(); let formData = new FormData();
let fileInput = document.getElementById('fileInput'); let fileInput = document.getElementById('fileInput-input');
let files = fileInput.files; let files = fileInput.files;
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
@ -177,7 +177,11 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
let listItem = document.createElement('li'); let listItem = document.createElement('li');
listItem.className = "list-group-item"; listItem.className = "list-group-item";
let hasSettings = (apiDocs[selectedOperation] && apiDocs[selectedOperation].post && let hasSettings = (apiDocs[selectedOperation] && apiDocs[selectedOperation].post &&
apiDocs[selectedOperation].post.parameters && apiDocs[selectedOperation].post.parameters.length > 0); ((apiDocs[selectedOperation].post.parameters && apiDocs[selectedOperation].post.parameters.length > 0) ||
(apiDocs[selectedOperation].post.requestBody &&
apiDocs[selectedOperation].post.requestBody.content['multipart/form-data'].schema.properties)));
listItem.innerHTML = ` listItem.innerHTML = `
@ -222,6 +226,13 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
let pipelineSettingsModal = document.getElementById('pipelineSettingsModal'); let pipelineSettingsModal = document.getElementById('pipelineSettingsModal');
let pipelineSettingsContent = document.getElementById('pipelineSettingsContent'); let pipelineSettingsContent = document.getElementById('pipelineSettingsContent');
let operationData = apiDocs[operation].post.parameters || []; let operationData = apiDocs[operation].post.parameters || [];
let requestBodyData = apiDocs[operation].post.requestBody.content['multipart/form-data'].schema.properties || {};
// Combine operationData and requestBodyData into a single array
operationData = operationData.concat(Object.keys(requestBodyData).map(key => ({
name: key,
schema: requestBodyData[key]
})));
pipelineSettingsContent.innerHTML = ''; pipelineSettingsContent.innerHTML = '';
@ -235,12 +246,39 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
parameterDiv.appendChild(parameterLabel); parameterDiv.appendChild(parameterLabel);
let parameterInput; let parameterInput;
// check if enum exists in schema
if (parameter.schema.enum) {
// if enum exists, create a select element
parameterInput = document.createElement('select');
parameterInput.className = "form-control";
// iterate over each enum value and create an option for it
parameter.schema.enum.forEach(value => {
let option = document.createElement('option');
option.value = value;
option.text = value;
parameterInput.appendChild(option);
});
} else {
// switch-case statement for handling non-enum types
switch (parameter.schema.type) { switch (parameter.schema.type) {
case 'string': case 'string':
if (parameter.schema.format === 'binary') {
// This is a file input
parameterInput = document.createElement('input');
parameterInput.type = 'file';
parameterInput.className = "form-control";
} else {
parameterInput = document.createElement('input');
parameterInput.type = 'text';
parameterInput.className = "form-control";
}
break;
case 'number': case 'number':
case 'integer': case 'integer':
parameterInput = document.createElement('input'); parameterInput = document.createElement('input');
parameterInput.type = parameter.schema.type === 'string' ? 'text' : 'number'; parameterInput.type = 'number';
parameterInput.className = "form-control"; parameterInput.className = "form-control";
break; break;
case 'boolean': case 'boolean':
@ -253,20 +291,11 @@ document.getElementById('addOperationBtn').addEventListener('click', function()
parameterInput.placeholder = `Enter a JSON formatted ${parameter.schema.type}`; parameterInput.placeholder = `Enter a JSON formatted ${parameter.schema.type}`;
parameterInput.className = "form-control"; parameterInput.className = "form-control";
break; break;
case 'enum':
parameterInput = document.createElement('select');
parameterInput.className = "form-control";
parameter.schema.enum.forEach(option => {
let optionElement = document.createElement('option');
optionElement.value = option;
optionElement.text = option;
parameterInput.appendChild(optionElement);
});
break;
default: default:
parameterInput = document.createElement('input'); parameterInput = document.createElement('input');
parameterInput.type = 'text'; parameterInput.type = 'text';
parameterInput.className = "form-control"; parameterInput.className = "form-control";
}
} }
parameterInput.id = parameter.name; parameterInput.id = parameter.name;

View file

@ -3,38 +3,68 @@
th:lang-direction="#{language.direction}" th:lang-direction="#{language.direction}"
xmlns:th="http://www.thymeleaf.org"> xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{pipeline.title})}"></th:block> <th:block
th:insert="~{fragments/common :: head(title=#{pipeline.title})}"></th:block>
<style>
.btn-margin {
margin-right: 2px;
}
.bordered-box {
border: 1px solid #ddd;
padding: 20px;
margin: 20px;
width: 70%;
}
.center-element {
width: 80%;
text-align: center;
margin: auto;
}
.element-margin {
margin: 10px 0; /* Adjust this value to increase/decrease the margin as needed */
}
</style>
<body> <body>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div> <div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br> <br> <br>
<div class="container" id="dropContainer"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<!-- Trigger/Open The Modal --> <div class="bordered-box">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#pipelineSettingsModal"> <div class="text-right text-top">
Open Pipeline Settings <button id="uploadPipelineBtn" class="btn btn-primary">Upload
</button> Custom</button>
<button type="button" class="btn btn-primary" data-toggle="modal"
data-target="#pipelineSettingsModal">Configure</button>
</div>
<div class="center-element">
<button id="uploadPipelineBtn" class="btn btn-primary">Upload Custom Pipeline</button> <div class="element-margin">
<select id="pipelineSelect"> <select id="pipelineSelect" class="custom-select">
<option value="">Select a pipeline</option> <option value="">Select a pipeline</option>
<th:block th:each="config : ${pipelineConfigsWithNames}"> <th:block th:each="config : ${pipelineConfigsWithNames}">
<option th:value="${config.json}" th:text="${config.name}"></option> <option th:value="${config.json}" th:text="${config.name}"></option>
</th:block> </th:block>
</select> </select>
</div>
<div class="element-margin">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=true)}"></div>
</div>
<div class="element-margin">
<button class="btn btn-primary" id="submitConfigBtn">Submit</button>
</div>
</div>
</div>
<!-- The Modal -->
<input type="file" id="fileInput" multiple> <div class="modal" id="pipelineSettingsModal">
<button class="btn btn-primary" id="submitConfigBtn">Submit</button>
<!-- The Modal -->
<div class="modal" id="pipelineSettingsModal">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -47,8 +77,9 @@
<!-- Modal body --> <!-- Modal body -->
<div class="modal-body"> <div class="modal-body">
<div class="mb-3"> <div class="mb-3">
<label for="pipelineName" class="form-label">Pipeline Name</label> <label for="pipelineName" class="form-label">Pipeline
<input type="text" id="pipelineName" class="form-control" placeholder="Enter pipeline name here"> Name</label> <input type="text" id="pipelineName"
class="form-control" placeholder="Enter pipeline name here">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<select id="operationsDropdown" class="form-select"> <select id="operationsDropdown" class="form-select">
@ -56,7 +87,8 @@
</select> </select>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<button id="addOperationBtn" class="btn btn-primary">Add operation</button> <button id="addOperationBtn" class="btn btn-primary">Add
operation</button>
</div> </div>
<h3>Pipeline:</h3> <h3>Pipeline:</h3>
<ol id="pipelineList" class="list-group"> <ol id="pipelineList" class="list-group">
@ -72,29 +104,21 @@
<button id="savePipelineBtn" class="btn btn-success">Download</button> <button id="savePipelineBtn" class="btn btn-success">Download</button>
<button id="validateButton" class="btn btn-success">Validate</button> <button id="validateButton" class="btn btn-success">Validate</button>
<div class="btn-group"> <div class="btn-group">
<input type="file" id="uploadPipelineInput" accept=".json" style="display: none;"> <input type="file" id="uploadPipelineInput" accept=".json"
style="display: none;">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script src="js/pipeline.js"></script>
<script src="js/pipeline.js"></script>
</div> </div>
</div> </div>
</div> </div>
<style>
.btn-margin {
margin-right: 2px;
}
</style>
<div th:insert="~{fragments/footer.html :: footer}"></div> <div th:insert="~{fragments/footer.html :: footer}"></div>
</div> </div>