mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-03 17:52:30 +02:00
init
This commit is contained in:
parent
54abb53842
commit
40e4fbabe6
@ -0,0 +1,158 @@
|
|||||||
|
package stirling.software.SPDF.controller;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.RenderedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
import java.util.zip.Deflater;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.contentstream.operator.Operator;
|
||||||
|
import org.apache.pdfbox.cos.COSDocument;
|
||||||
|
import org.apache.pdfbox.cos.COSName;
|
||||||
|
import org.apache.pdfbox.pdfparser.PDFParser;
|
||||||
|
import org.apache.pdfbox.pdfparser.PDFStreamParser;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDResources;
|
||||||
|
import org.apache.pdfbox.pdmodel.common.PDStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import com.itextpdf.text.pdf.PdfDocument;
|
||||||
|
import com.itextpdf.text.pdf.PdfPage;
|
||||||
|
import com.itextpdf.text.pdf.PdfReader;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class ExtractImagesController {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ExtractImagesController.class);
|
||||||
|
|
||||||
|
@GetMapping("/extract-images")
|
||||||
|
public String extractImagesForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "extract-images");
|
||||||
|
return "extract-images";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/extract-images")
|
||||||
|
public ResponseEntity<Resource> extractImages(@RequestParam("fileInput") MultipartFile file, @RequestParam("format") String format) throws IOException {
|
||||||
|
|
||||||
|
System.out.println(System.currentTimeMillis() + "file=" + file.getName() + ", format=" + format);
|
||||||
|
PDDocument document = PDDocument.load(file.getBytes());
|
||||||
|
|
||||||
|
// Create ByteArrayOutputStream to write zip file to byte array
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
// Create ZipOutputStream to create zip file
|
||||||
|
ZipOutputStream zos = new ZipOutputStream(baos);
|
||||||
|
|
||||||
|
// Set compression level
|
||||||
|
zos.setLevel(Deflater.BEST_COMPRESSION);
|
||||||
|
|
||||||
|
int imageIndex = 1;
|
||||||
|
|
||||||
|
int pageNum = 1;
|
||||||
|
// Iterate over each page
|
||||||
|
for (PDPage page : document.getPages()) {
|
||||||
|
++pageNum;
|
||||||
|
// Extract images from page
|
||||||
|
for (COSName name : page.getResources().getXObjectNames()) {
|
||||||
|
if (page.getResources().isImageXObject(name)) {
|
||||||
|
PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);
|
||||||
|
|
||||||
|
// Convert image to desired format
|
||||||
|
RenderedImage renderedImage = image.getImage();
|
||||||
|
BufferedImage bufferedImage = null;
|
||||||
|
if (format.equalsIgnoreCase("png")) {
|
||||||
|
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(),
|
||||||
|
BufferedImage.TYPE_INT_ARGB);
|
||||||
|
} else if (format.equalsIgnoreCase("jpeg") || format.equalsIgnoreCase("jpg")) {
|
||||||
|
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(),
|
||||||
|
BufferedImage.TYPE_INT_RGB);
|
||||||
|
} else if (format.equalsIgnoreCase("gif")) {
|
||||||
|
bufferedImage = new BufferedImage(renderedImage.getWidth(), renderedImage.getHeight(),
|
||||||
|
BufferedImage.TYPE_BYTE_INDEXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write image to zip file
|
||||||
|
String imageName = "Image " + imageIndex + " (Page " + pageNum + ")." + format;
|
||||||
|
ZipEntry zipEntry = new ZipEntry(imageName);
|
||||||
|
zos.putNextEntry(zipEntry);
|
||||||
|
|
||||||
|
Graphics2D g = bufferedImage.createGraphics();
|
||||||
|
g.drawImage((Image) renderedImage, 0, 0, null);
|
||||||
|
g.dispose();
|
||||||
|
// Write image bytes to zip file
|
||||||
|
ByteArrayOutputStream imageBaos = new ByteArrayOutputStream();
|
||||||
|
ImageIO.write(bufferedImage, format, imageBaos);
|
||||||
|
zos.write(imageBaos.toByteArray());
|
||||||
|
|
||||||
|
|
||||||
|
zos.closeEntry();
|
||||||
|
imageIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close ZipOutputStream and PDDocument
|
||||||
|
zos.close();
|
||||||
|
document.close();
|
||||||
|
|
||||||
|
// Create ByteArrayResource from byte array
|
||||||
|
byte[] zipContents = baos.toByteArray();
|
||||||
|
ByteArrayResource resource = new ByteArrayResource(zipContents);
|
||||||
|
|
||||||
|
// Set content disposition header to indicate that the response should be downloaded as a file
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentLength(zipContents.length);
|
||||||
|
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted-images.zip");
|
||||||
|
|
||||||
|
// Return ResponseEntity with ByteArrayResource and headers
|
||||||
|
return ResponseEntity
|
||||||
|
.status(HttpStatus.OK)
|
||||||
|
.headers(headers)
|
||||||
|
|
||||||
|
.header("Cache-Control", "no-cache")
|
||||||
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
.body(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
112
src/main/resources/templates/extract-images.html
Normal file
112
src/main/resources/templates/extract-images.html
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
|
||||||
|
|
||||||
|
<th:block th:insert="~{fragments/common :: head(title=#{extractImages.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="#{extractImages.header}"></h2>
|
||||||
|
|
||||||
|
<form id="multiPdfForm" th:action="@{extract-images}" method="post" enctype="multipart/form-data">
|
||||||
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=true)}"></div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label th:text="#{extractImages.selectText}"></label>
|
||||||
|
<select class="form-control" name="format">
|
||||||
|
<option value="png">PNG</option>
|
||||||
|
<option value="jpg">JPG</option>
|
||||||
|
<option value="gif">GIF</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary" th:text="#{extractImages.submit}"></button>
|
||||||
|
</form>
|
||||||
|
<th:block th:insert="~{fragments/common :: filelist}"></th:block>
|
||||||
|
<div id="progressBarContainer" style="display: none;">
|
||||||
|
<div class="progress">
|
||||||
|
<div id="progressBar" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
$('#multiPdfForm').submit(function(event) {
|
||||||
|
event.preventDefault(); // Prevent the default form handling behavior
|
||||||
|
|
||||||
|
|
||||||
|
submitMultiPdfForm(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function submitMultiPdfForm(event) {
|
||||||
|
|
||||||
|
|
||||||
|
// Get the selected PDF files
|
||||||
|
var files = $('#fileInput-input')[0].files;
|
||||||
|
|
||||||
|
// Get the existing form data
|
||||||
|
var formData = new FormData($('form')[0]);
|
||||||
|
formData.delete('fileInput');
|
||||||
|
|
||||||
|
// Show the progress bar
|
||||||
|
$('#progressBarContainer').show();
|
||||||
|
|
||||||
|
// Submit each PDF file sequentially
|
||||||
|
for (var i = 0; i < files.length; i++) {
|
||||||
|
formData.append('fileInput', files[i]);
|
||||||
|
|
||||||
|
console.log('Submitting request for file ' + i);
|
||||||
|
|
||||||
|
// Initialize the progress bar
|
||||||
|
var progressBar = $('#progressBar');
|
||||||
|
progressBar.css('width', '0%');
|
||||||
|
progressBar.attr('aria-valuenow', 0);
|
||||||
|
progressBar.attr('aria-valuemax', files.length);
|
||||||
|
|
||||||
|
await fetch('extract-images', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(function(response) {
|
||||||
|
console.log('Received response for file ' + i + ': ' + response);
|
||||||
|
|
||||||
|
var contentDisposition = response.headers.get('content-disposition');
|
||||||
|
var fileName = contentDisposition.split('filename=')[1].replace(/"/g, '');
|
||||||
|
|
||||||
|
response.blob().then(function(blob) {
|
||||||
|
var url = window.URL.createObjectURL(blob);
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileName;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
});
|
||||||
|
}).catch(function(error) {
|
||||||
|
console.error('Error submitting request for file ' + i + ': ' + error);
|
||||||
|
}).finally(function() {
|
||||||
|
|
||||||
|
// Update the progress bar
|
||||||
|
var progress = Math.round(((i + 1) / files.length) * 100);
|
||||||
|
|
||||||
|
console.log('progress ' + progress);
|
||||||
|
progressBar.css('width', progress + '%');
|
||||||
|
progressBar.attr('aria-valuenow', i + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div th:insert="~{fragments/footer.html :: footer}"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -67,6 +67,7 @@
|
|||||||
<a class="nav-link" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''"></a>
|
<a class="nav-link" href="#" th:href="@{compress-pdf}" th:classappend="${currentPage}=='compress-pdf' ? 'active' : ''"></a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
<input type="checkbox" id="toggle-dark-mode" checked="true" th:onclick="javascript:toggleDarkMode()">
|
<input type="checkbox" id="toggle-dark-mode" checked="true" th:onclick="javascript:toggleDarkMode()">
|
||||||
<a class="nav-link" href="#" for="toggle-dark-mode" th:text="#{navbar.darkmode}"></a>
|
<a class="nav-link" href="#" for="toggle-dark-mode" th:text="#{navbar.darkmode}"></a>
|
||||||
|
|
||||||
@ -86,6 +87,11 @@
|
|||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<ul class="navbar-nav ml-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<button type="button" class="btn btn-outline-primary" id="update-btn">Update available</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Get the dropdown items
|
// Get the dropdown items
|
||||||
|
Loading…
Reference in New Issue
Block a user