diff --git a/app/common/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/app/common/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index 326a49e709..f611ce7e09 100644 --- a/app/common/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/app/common/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -373,7 +373,6 @@ public class EndpointConfiguration { addEndpointToGroup("Other", REMOVE_BLANKS); addEndpointToGroup("Other", "remove-annotations"); addEndpointToGroup("Other", "get-info-on-pdf"); - addEndpointToGroup("Other", "remove-image-pdf"); addEndpointToGroup("Other", "add-attachments"); addEndpointToGroup("Other", "replace-invert-pdf"); addEndpointToGroup("Other", "edit-table-of-contents"); @@ -488,7 +487,6 @@ public class EndpointConfiguration { addEndpointToGroup("Java", REMOVE_BLANKS); addEndpointToGroup("Java", "remove-annotations"); addEndpointToGroup("Java", "pdf-to-text"); - addEndpointToGroup("Java", "remove-image-pdf"); addEndpointToGroup("Java", "pdf-to-markdown"); addEndpointToGroup("Java", "add-attachments"); addEndpointToGroup("Java", "compress-pdf"); diff --git a/app/common/src/test/java/stirling/software/common/util/ErrorUtilsTest.java b/app/common/src/test/java/stirling/software/common/util/ErrorUtilsTest.java deleted file mode 100644 index d1da37ae58..0000000000 --- a/app/common/src/test/java/stirling/software/common/util/ErrorUtilsTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package stirling.software.common.util; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Test; -import org.springframework.ui.Model; -import org.springframework.web.servlet.ModelAndView; - -public class ErrorUtilsTest { - - @Test - public void testExceptionToModel() { - // Create a mock Model - Model model = new org.springframework.ui.ExtendedModelMap(); - - // Create a test exception - Exception ex = new Exception("Test Exception"); - - // Call the method under test - Model resultModel = ErrorUtils.exceptionToModel(model, ex); - - // Verify the result - assertNotNull(resultModel); - assertEquals("Test Exception", resultModel.getAttribute("errorMessage")); - assertNotNull(resultModel.getAttribute("stackTrace")); - } - - @Test - public void testExceptionToModelView() { - // Create a mock Model - Model model = new org.springframework.ui.ExtendedModelMap(); - - // Create a test exception - Exception ex = new Exception("Test Exception"); - - // Call the method under test - ModelAndView modelAndView = ErrorUtils.exceptionToModelView(model, ex); - - // Verify the result - assertNotNull(modelAndView); - assertEquals("Test Exception", modelAndView.getModel().get("errorMessage")); - assertNotNull(modelAndView.getModel().get("stackTrace")); - } -} diff --git a/app/common/src/test/java/stirling/software/common/util/FileMonitorTest.java b/app/common/src/test/java/stirling/software/common/util/FileMonitorTest.java deleted file mode 100644 index 851c1c6cd1..0000000000 --- a/app/common/src/test/java/stirling/software/common/util/FileMonitorTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package stirling.software.common.util; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.time.Instant; -import java.util.List; -import java.util.function.Predicate; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -import stirling.software.common.configuration.RuntimePathConfig; - -@ExtendWith(MockitoExtension.class) -class FileMonitorTest { - - @TempDir Path tempDir; - - @Mock private RuntimePathConfig runtimePathConfig; - - @Mock private Predicate pathFilter; - - private FileMonitor fileMonitor; - - @BeforeEach - void setUp() throws IOException { - when(runtimePathConfig.getPipelineWatchedFoldersPaths()) - .thenReturn(List.of(tempDir.toString())); - - // This mock is used in all tests except testPathFilter - // We use lenient to avoid UnnecessaryStubbingException in that test - Mockito.lenient().when(pathFilter.test(any())).thenReturn(true); - - fileMonitor = new FileMonitor(pathFilter, runtimePathConfig); - } - - @Test - void testIsFileReadyForProcessing_OldFile() throws IOException { - // Capture test time at the beginning for deterministic calculations - final Instant testTime = Instant.now(); - - // Create a test file - Path testFile = tempDir.resolve("test-file.txt"); - Files.write(testFile, "test content".getBytes()); - - // Set modified time to 10 seconds ago (relative to test start time) - Files.setLastModifiedTime(testFile, FileTime.from(testTime.minusMillis(10000))); - - // File should be ready for processing as it was modified more than 5 seconds ago - assertTrue(fileMonitor.isFileReadyForProcessing(testFile)); - } - - @Test - void testIsFileReadyForProcessing_RecentFile() throws IOException { - // Capture test time at the beginning for deterministic calculations - final Instant testTime = Instant.now(); - - // Create a test file - Path testFile = tempDir.resolve("recent-file.txt"); - Files.write(testFile, "test content".getBytes()); - - // Set modified time to just now (relative to test start time) - Files.setLastModifiedTime(testFile, FileTime.from(testTime)); - - // File should not be ready for processing as it was just modified - assertFalse(fileMonitor.isFileReadyForProcessing(testFile)); - } - - @Test - void testIsFileReadyForProcessing_NonExistentFile() { - // Create a path to a file that doesn't exist - Path nonExistentFile = tempDir.resolve("non-existent-file.txt"); - - // Non-existent file should not be ready for processing - assertFalse(fileMonitor.isFileReadyForProcessing(nonExistentFile)); - } - - @Test - void testIsFileReadyForProcessing_LockedFile() throws IOException { - // Capture test time at the beginning for deterministic calculations - final Instant testTime = Instant.now(); - - // Create a test file - Path testFile = tempDir.resolve("locked-file.txt"); - Files.write(testFile, "test content".getBytes()); - - // Set modified time to 10 seconds ago (relative to test start time) to make sure it passes - // the time check - Files.setLastModifiedTime(testFile, FileTime.from(testTime.minusMillis(10000))); - - // Verify the file is considered ready when it meets the time criteria - assertTrue( - fileMonitor.isFileReadyForProcessing(testFile), - "File should be ready for processing when sufficiently old"); - } - - @Test - void testPathFilter() throws IOException { - // Use a simple lambda instead of a mock for better control - Predicate pdfFilter = path -> path.toString().endsWith(".pdf"); - - // Create a new FileMonitor with the PDF filter - FileMonitor pdfMonitor = new FileMonitor(pdfFilter, runtimePathConfig); - - // Create a PDF file - Path pdfFile = tempDir.resolve("test.pdf"); - Files.write(pdfFile, "pdf content".getBytes()); - Files.setLastModifiedTime(pdfFile, FileTime.from(Instant.ofEpochMilli(1000000L))); - - // Create a TXT file - Path txtFile = tempDir.resolve("test.txt"); - Files.write(txtFile, "text content".getBytes()); - Files.setLastModifiedTime(txtFile, FileTime.from(Instant.ofEpochMilli(1000000L))); - - // PDF file should be ready for processing - assertTrue(pdfMonitor.isFileReadyForProcessing(pdfFile)); - - // Note: In the current implementation, FileMonitor.isFileReadyForProcessing() - // doesn't check file filters directly - it only checks criteria like file existence - // and modification time. The filtering is likely handled elsewhere in the workflow. - - // To avoid test failures, we'll verify that the filter itself works correctly - assertFalse(pdfFilter.test(txtFile), "PDF filter should reject txt files"); - assertTrue(pdfFilter.test(pdfFile), "PDF filter should accept pdf files"); - } - - @Test - void testIsFileReadyForProcessing_FileInUse() throws IOException { - // Capture test time at the beginning for deterministic calculations - final Instant testTime = Instant.now(); - - // Create a test file - Path testFile = tempDir.resolve("in-use-file.txt"); - Files.write(testFile, "initial content".getBytes()); - - // Set modified time to 10 seconds ago (relative to test start time) - Files.setLastModifiedTime(testFile, FileTime.from(testTime.minusMillis(10000))); - - // First check that the file is ready when meeting time criteria - assertTrue( - fileMonitor.isFileReadyForProcessing(testFile), - "File should be ready for processing when sufficiently old"); - - // After modifying the file to simulate closing, it should still be ready - Files.write(testFile, "updated content".getBytes()); - Files.setLastModifiedTime(testFile, FileTime.from(testTime.minusMillis(10000))); - - assertTrue( - fileMonitor.isFileReadyForProcessing(testFile), - "File should be ready for processing after updating"); - } - - @Test - void testIsFileReadyForProcessing_FileWithAbsolutePath() throws IOException { - // Capture test time at the beginning for deterministic calculations - final Instant testTime = Instant.now(); - - // Create a test file - Path testFile = tempDir.resolve("absolute-path-file.txt"); - Files.write(testFile, "test content".getBytes()); - - // Set modified time to 10 seconds ago (relative to test start time) - Files.setLastModifiedTime(testFile, FileTime.from(testTime.minusMillis(10000))); - - // File should be ready for processing as it was modified more than 5 seconds ago - // Use the absolute path to make sure it's handled correctly - assertTrue(fileMonitor.isFileReadyForProcessing(testFile.toAbsolutePath())); - } - - @Test - void testIsFileReadyForProcessing_DirectoryInsteadOfFile() throws IOException { - // Create a test directory - Path testDir = tempDir.resolve("test-directory"); - Files.createDirectory(testDir); - - // Set modified time to 10 seconds ago - Files.setLastModifiedTime(testDir, FileTime.from(Instant.ofEpochMilli(1000000L))); - - // A directory should not be considered ready for processing - boolean isReady = fileMonitor.isFileReadyForProcessing(testDir); - assertFalse(isReady, "A directory should not be considered ready for processing"); - } -} diff --git a/app/common/src/test/java/stirling/software/common/util/FileToPdfTest.java b/app/common/src/test/java/stirling/software/common/util/FileToPdfTest.java deleted file mode 100644 index 5a98bdbb76..0000000000 --- a/app/common/src/test/java/stirling/software/common/util/FileToPdfTest.java +++ /dev/null @@ -1,115 +0,0 @@ -package stirling.software.common.util; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.nio.file.Files; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import stirling.software.common.model.api.converters.HTMLToPdfRequest; -import stirling.software.common.service.SsrfProtectionService; - -public class FileToPdfTest { - - private CustomHtmlSanitizer customHtmlSanitizer; - - @BeforeEach - void setUp() { - SsrfProtectionService mockSsrfProtectionService = mock(SsrfProtectionService.class); - stirling.software.common.model.ApplicationProperties mockApplicationProperties = - mock(stirling.software.common.model.ApplicationProperties.class); - stirling.software.common.model.ApplicationProperties.System mockSystem = - mock(stirling.software.common.model.ApplicationProperties.System.class); - - when(mockSsrfProtectionService.isUrlAllowed(org.mockito.ArgumentMatchers.anyString())) - .thenReturn(true); - when(mockApplicationProperties.getSystem()).thenReturn(mockSystem); - when(mockSystem.isDisableSanitize()).thenReturn(false); - - customHtmlSanitizer = - new CustomHtmlSanitizer(mockSsrfProtectionService, mockApplicationProperties); - } - - /** - * Test the HTML to PDF conversion. This test expects an IOException when an empty HTML input is - * provided. - */ - @Test - public void testConvertHtmlToPdf() { - HTMLToPdfRequest request = new HTMLToPdfRequest(); - byte[] fileBytes = new byte[0]; // Sample file bytes (empty input) - String fileName = "test.html"; // Sample file name indicating an HTML file - TempFileManager tempFileManager = mock(TempFileManager.class); // Mock TempFileManager - - // Mock the temp file creation to return real temp files - try { - when(tempFileManager.createTempFile(anyString())) - .thenReturn(Files.createTempFile("test", ".pdf").toFile()) - .thenReturn(Files.createTempFile("test", ".html").toFile()); - } catch (IOException e) { - throw new RuntimeException(e); - } - - // Expect an IOException to be thrown due to empty input or invalid weasyprint path - Throwable thrown = - assertThrows( - Exception.class, - () -> - FileToPdf.convertHtmlToPdf( - "/path/", - request, - fileBytes, - fileName, - tempFileManager, - customHtmlSanitizer)); - assertNotNull(thrown); - } - - /** - * Test sanitizeZipFilename with null or empty input. It should return an empty string in these - * cases. - */ - @Test - public void testSanitizeZipFilename_NullOrEmpty() { - assertEquals("", FileToPdf.sanitizeZipFilename(null)); - assertEquals("", FileToPdf.sanitizeZipFilename(" ")); - } - - /** - * Test sanitizeZipFilename to ensure it removes path traversal sequences. This includes - * removing both forward and backward slash sequences. - */ - @Test - public void testSanitizeZipFilename_RemovesTraversalSequences() { - String input = "../some/../path/..\\to\\file.txt"; - String expected = "some/path/to/file.txt"; - - // Expect that the method replaces backslashes with forward slashes - // and removes path traversal sequences - assertEquals(expected, FileToPdf.sanitizeZipFilename(input)); - } - - /** Test sanitizeZipFilename to ensure that it removes leading drive letters and slashes. */ - @Test - public void testSanitizeZipFilename_RemovesLeadingDriveAndSlashes() { - String input = "C:\\folder\\file.txt"; - String expected = "folder/file.txt"; - assertEquals(expected, FileToPdf.sanitizeZipFilename(input)); - - input = "/folder/file.txt"; - expected = "folder/file.txt"; - assertEquals(expected, FileToPdf.sanitizeZipFilename(input)); - } - - /** Test sanitizeZipFilename to verify that safe filenames remain unchanged. */ - @Test - public void testSanitizeZipFilename_NoChangeForSafeNames() { - String input = "folder/subfolder/file.txt"; - assertEquals(input, FileToPdf.sanitizeZipFilename(input)); - } -} diff --git a/app/common/src/test/java/stirling/software/common/util/ImageProcessingUtilsTest.java b/app/common/src/test/java/stirling/software/common/util/ImageProcessingUtilsTest.java deleted file mode 100644 index 418a8c9bec..0000000000 --- a/app/common/src/test/java/stirling/software/common/util/ImageProcessingUtilsTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package stirling.software.common.util; - -import static org.junit.jupiter.api.Assertions.*; - -import java.awt.*; -import java.awt.image.BufferedImage; - -import org.junit.jupiter.api.Test; - -public class ImageProcessingUtilsTest { - - private static void fillImageWithColor(BufferedImage image) { - for (int y = 0; y < image.getHeight(); y++) { - for (int x = 0; x < image.getWidth(); x++) { - image.setRGB(x, y, Color.RED.getRGB()); - } - } - } - - @Test - void testConvertColorTypeToGreyscale() { - BufferedImage sourceImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); - fillImageWithColor(sourceImage); - - BufferedImage convertedImage = - ImageProcessingUtils.convertColorType(sourceImage, "greyscale"); - - assertNotNull(convertedImage); - assertEquals(BufferedImage.TYPE_BYTE_GRAY, convertedImage.getType()); - assertEquals(sourceImage.getWidth(), convertedImage.getWidth()); - assertEquals(sourceImage.getHeight(), convertedImage.getHeight()); - - // Check if a pixel is correctly converted to greyscale - Color grey = new Color(convertedImage.getRGB(0, 0)); - assertEquals(grey.getRed(), grey.getGreen()); - assertEquals(grey.getGreen(), grey.getBlue()); - } - - @Test - void testConvertColorTypeToBlackWhite() { - BufferedImage sourceImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); - fillImageWithColor(sourceImage); - - BufferedImage convertedImage = - ImageProcessingUtils.convertColorType(sourceImage, "blackwhite"); - - assertNotNull(convertedImage); - assertEquals(BufferedImage.TYPE_BYTE_BINARY, convertedImage.getType()); - assertEquals(sourceImage.getWidth(), convertedImage.getWidth()); - assertEquals(sourceImage.getHeight(), convertedImage.getHeight()); - - // Check if a pixel is converted correctly (binary image will be either black or white) - int rgb = convertedImage.getRGB(0, 0); - assertTrue(rgb == Color.BLACK.getRGB() || rgb == Color.WHITE.getRGB()); - } - - @Test - void testConvertColorTypeToFullColor() { - BufferedImage sourceImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); - fillImageWithColor(sourceImage); - - BufferedImage convertedImage = - ImageProcessingUtils.convertColorType(sourceImage, "fullcolor"); - - assertNotNull(convertedImage); - assertEquals(sourceImage, convertedImage); - } - - @Test - void testConvertColorTypeInvalid() { - BufferedImage sourceImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); - fillImageWithColor(sourceImage); - - BufferedImage convertedImage = - ImageProcessingUtils.convertColorType(sourceImage, "invalidtype"); - - assertNotNull(convertedImage); - assertEquals(sourceImage, convertedImage); - } -} diff --git a/app/common/src/test/java/stirling/software/common/util/PdfUtilsTest.java b/app/common/src/test/java/stirling/software/common/util/PdfUtilsTest.java deleted file mode 100644 index fd7854020b..0000000000 --- a/app/common/src/test/java/stirling/software/common/util/PdfUtilsTest.java +++ /dev/null @@ -1,736 +0,0 @@ -package stirling.software.common.util; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.RenderedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import javax.imageio.ImageIO; - -import org.apache.pdfbox.cos.COSName; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDPageContentStream; -import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode; -import org.apache.pdfbox.pdmodel.PDResources; -import org.apache.pdfbox.pdmodel.common.PDRectangle; -import org.apache.pdfbox.pdmodel.font.PDType1Font; -import org.apache.pdfbox.pdmodel.font.Standard14Fonts; -import org.apache.pdfbox.pdmodel.graphics.PDXObject; -import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; -import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; -import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; -import org.apache.pdfbox.rendering.ImageType; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; -import org.springframework.mock.web.MockMultipartFile; - -import stirling.software.common.model.ApplicationProperties; -import stirling.software.common.service.CustomPDFDocumentFactory; -import stirling.software.common.service.PdfMetadataService; - -public class PdfUtilsTest { - - @Test - void testTextToPageSize() { - assertEquals(PDRectangle.A0, PdfUtils.textToPageSize("A0")); - assertEquals(PDRectangle.A1, PdfUtils.textToPageSize("A1")); - assertEquals(PDRectangle.A2, PdfUtils.textToPageSize("A2")); - assertEquals(PDRectangle.A3, PdfUtils.textToPageSize("A3")); - assertEquals(PDRectangle.A4, PdfUtils.textToPageSize("A4")); - assertEquals(PDRectangle.A5, PdfUtils.textToPageSize("A5")); - assertEquals(PDRectangle.A6, PdfUtils.textToPageSize("A6")); - assertEquals(PDRectangle.LETTER, PdfUtils.textToPageSize("LETTER")); - assertEquals(PDRectangle.LEGAL, PdfUtils.textToPageSize("LEGAL")); - assertThrows(IllegalArgumentException.class, () -> PdfUtils.textToPageSize("INVALID")); - } - - @Test - void testGetAllImages() throws Exception { - // Root resources - PDResources root = mock(PDResources.class); - - COSName im1 = COSName.getPDFName("Im1"); - COSName form1 = COSName.getPDFName("Form1"); - COSName other1 = COSName.getPDFName("Other1"); - when(root.getXObjectNames()).thenReturn(Arrays.asList(im1, form1, other1)); - - // Direct image at root - PDImageXObject imgXObj1 = mock(PDImageXObject.class); - BufferedImage img1 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB); - when(imgXObj1.getImage()).thenReturn(img1); - when(root.getXObject(im1)).thenReturn(imgXObj1); - - // "Other" XObject that should be ignored - PDXObject otherXObj = mock(PDXObject.class); - when(root.getXObject(other1)).thenReturn(otherXObj); - - // Form XObject with its own resources - PDFormXObject formXObj = mock(PDFormXObject.class); - PDResources formRes = mock(PDResources.class); - when(formXObj.getResources()).thenReturn(formRes); - when(root.getXObject(form1)).thenReturn(formXObj); - - // Inside the form: one image and a nested form - COSName im2 = COSName.getPDFName("Im2"); - COSName nestedForm = COSName.getPDFName("NestedForm"); - when(formRes.getXObjectNames()).thenReturn(Arrays.asList(im2, nestedForm)); - - PDImageXObject imgXObj2 = mock(PDImageXObject.class); - BufferedImage img2 = new BufferedImage(3, 3, BufferedImage.TYPE_INT_RGB); - when(imgXObj2.getImage()).thenReturn(img2); - when(formRes.getXObject(im2)).thenReturn(imgXObj2); - - PDFormXObject nestedFormXObj = mock(PDFormXObject.class); - PDResources nestedRes = mock(PDResources.class); - when(nestedFormXObj.getResources()).thenReturn(nestedRes); - when(formRes.getXObject(nestedForm)).thenReturn(nestedFormXObj); - - // Deep nest: another image - COSName im3 = COSName.getPDFName("Im3"); - when(nestedRes.getXObjectNames()).thenReturn(List.of(im3)); - - PDImageXObject imgXObj3 = mock(PDImageXObject.class); - BufferedImage img3 = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); - when(imgXObj3.getImage()).thenReturn(img3); - when(nestedRes.getXObject(im3)).thenReturn(imgXObj3); - - // Act - List result = PdfUtils.getAllImages(root); - - // Assert - assertEquals( - 3, result.size(), "It should find exactly 3 images (root + form + nested form)."); - assertTrue( - result.containsAll(List.of(img1, img2, img3)), - "All expected images must be present."); - } - - @Test - void testPageCountComparators() throws Exception { - PDDocument doc1 = new PDDocument(); - doc1.addPage(new PDPage()); - doc1.addPage(new PDPage()); - doc1.addPage(new PDPage()); - assertTrue(PdfUtils.pageCount(doc1, 2, "greater")); - - PDDocument doc2 = new PDDocument(); - doc2.addPage(new PDPage()); - doc2.addPage(new PDPage()); - doc2.addPage(new PDPage()); - assertTrue(PdfUtils.pageCount(doc2, 3, "equal")); - - PDDocument doc3 = new PDDocument(); - doc3.addPage(new PDPage()); - doc3.addPage(new PDPage()); - assertTrue(PdfUtils.pageCount(doc3, 5, "less")); - - PDDocument doc4 = new PDDocument(); - doc4.addPage(new PDPage()); - assertThrows(IllegalArgumentException.class, () -> PdfUtils.pageCount(doc4, 1, "bad")); - } - - @Test - void testPageSize() throws Exception { - PDDocument doc = new PDDocument(); - PDPage page = new PDPage(PDRectangle.A4); - doc.addPage(page); - PDRectangle rect = page.getMediaBox(); - String expected = rect.getWidth() + "x" + rect.getHeight(); - assertTrue(PdfUtils.pageSize(doc, expected)); - } - - @Test - void testOverlayImage() throws Exception { - PDDocument doc = new PDDocument(); - doc.addPage(new PDPage(PDRectangle.A4)); - ByteArrayOutputStream pdfOut = new ByteArrayOutputStream(); - doc.save(pdfOut); - doc.close(); - - BufferedImage image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); - Graphics2D g = image.createGraphics(); - g.setColor(Color.RED); - g.fillRect(0, 0, 10, 10); - g.dispose(); - ByteArrayOutputStream imgOut = new ByteArrayOutputStream(); - ImageIO.write(image, "png", imgOut); - - PdfMetadataService meta = - new PdfMetadataService(new ApplicationProperties(), "label", false, null); - CustomPDFDocumentFactory factory = new CustomPDFDocumentFactory(meta); - - byte[] result = - PdfUtils.overlayImage( - factory, pdfOut.toByteArray(), imgOut.toByteArray(), 0, 0, false); - try (PDDocument resultDoc = factory.load(result)) { - assertEquals(1, resultDoc.getNumberOfPages()); - } - } - - // =============================================================== - // Additional tests (added without modifying existing ones) - // =============================================================== - - /* Helper: create a colored test image */ - private static BufferedImage createImage(int w, int h, Color color) { - BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); - Graphics2D g = img.createGraphics(); - g.setColor(color); - g.fillRect(0, 0, w, h); - g.dispose(); - return img; - } - - /* Helper: create a factory like in existing tests */ - private static CustomPDFDocumentFactory factory() { - PdfMetadataService meta = - new PdfMetadataService(new ApplicationProperties(), "label", false, null); - return new CustomPDFDocumentFactory(meta); - } - - @Test - @DisplayName("convertPdfToPdfImage: creates image-PDF with same page count") - void convertPdfToPdfImage_shouldCreateImagePdfWithSamePageCount() throws IOException { - try (PDDocument doc = new PDDocument()) { - PDPage p1 = new PDPage(PDRectangle.A4); - doc.addPage(p1); - try (PDPageContentStream cs = - new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) { - cs.beginText(); - cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); - cs.newLineAtOffset(50, 750); - cs.showText("Hello PDF"); - cs.endText(); - } - PDPage p2 = new PDPage(PDRectangle.A4); - doc.addPage(p2); - - PDDocument out = PdfUtils.convertPdfToPdfImage(doc); - assertNotNull(out); - assertEquals(2, out.getNumberOfPages(), "Page count should be preserved"); - out.close(); - } - } - - @Test - @DisplayName("imageToPdf: PNG -> single-page PDF (static ImageProcessingUtils mocked)") - void imageToPdf_shouldCreatePdfFromPng() throws Exception { - BufferedImage img = createImage(320, 200, Color.RED); - ByteArrayOutputStream pngOut = new ByteArrayOutputStream(); - ImageIO.write(img, "png", pngOut); - - MockMultipartFile file = - new MockMultipartFile("files", "test.png", "image/png", pngOut.toByteArray()); - - try (MockedStatic mocked = mockStatic(ImageProcessingUtils.class)) { - // Assume: loadImageWithExifOrientation/convertColorType exist – static mock - mocked.when(() -> ImageProcessingUtils.loadImageWithExifOrientation(any())) - .thenReturn(img); - mocked.when( - () -> - ImageProcessingUtils.convertColorType( - any(BufferedImage.class), anyString())) - .thenAnswer(inv -> inv.getArgument(0, BufferedImage.class)); - - byte[] pdfBytes = - PdfUtils.imageToPdf( - new MockMultipartFile[] {file}, - "maintainAspectRatio", - true, - "RGB", - factory()); - - try (PDDocument result = factory().load(pdfBytes)) { - assertEquals(1, result.getNumberOfPages()); - } - } - } - - @Test - @DisplayName("imageToPdf: JPEG -> single-page PDF (JPEGFactory path)") - void imageToPdf_shouldCreatePdfFromJpeg_UsingJpegFactory() throws Exception { - BufferedImage img = createImage(640, 360, Color.BLUE); - ByteArrayOutputStream jpgOut = new ByteArrayOutputStream(); - ImageIO.write(img, "jpg", jpgOut); - - MockMultipartFile file = - new MockMultipartFile("files", "photo.jpg", "image/jpeg", jpgOut.toByteArray()); - - try (MockedStatic mocked = mockStatic(ImageProcessingUtils.class)) { - mocked.when(() -> ImageProcessingUtils.loadImageWithExifOrientation(any())) - .thenReturn(img); - mocked.when( - () -> - ImageProcessingUtils.convertColorType( - any(BufferedImage.class), anyString())) - .thenAnswer(inv -> inv.getArgument(0, BufferedImage.class)); - - byte[] pdfBytes = - PdfUtils.imageToPdf( - new MockMultipartFile[] {file}, "fillPage", false, "RGB", factory()); - - try (PDDocument result = factory().load(pdfBytes)) { - assertEquals(1, result.getNumberOfPages()); - } - } - } - - @Test - @DisplayName("addImageToDocument: fitDocumentToImage -> page size = image size") - void addImageToDocument_shouldUseImageSizeForPage_whenFitDocumentToImage() throws IOException { - try (PDDocument doc = new PDDocument()) { - BufferedImage img = createImage(300, 500, Color.GREEN); - PDImageXObject ximg = LosslessFactory.createFromImage(doc, img); - - PdfUtils.addImageToDocument(doc, ximg, "fitDocumentToImage", false); - - assertEquals(1, doc.getNumberOfPages()); - PDRectangle box = doc.getPage(0).getMediaBox(); - assertEquals(300, (int) box.getWidth()); - assertEquals(500, (int) box.getHeight()); - } - } - - @Test - @DisplayName("addImageToDocument: autoRotate rotates A4 for landscape image") - void addImageToDocument_shouldRotateA4_whenAutoRotateAndLandscape() throws IOException { - try (PDDocument doc = new PDDocument()) { - BufferedImage img = createImage(800, 400, Color.ORANGE); // Landscape - PDImageXObject ximg = LosslessFactory.createFromImage(doc, img); - - PdfUtils.addImageToDocument(doc, ximg, "maintainAspectRatio", true); - - assertEquals(1, doc.getNumberOfPages()); - PDRectangle box = doc.getPage(0).getMediaBox(); - assertTrue( - box.getWidth() > box.getHeight(), - "A4 should be landscape when auto-rotate + landscape"); - } - } - - @Test - @DisplayName("addImageToDocument: fillPage runs without errors") - void addImageToDocument_fillPage_executes() throws IOException { - try (PDDocument doc = new PDDocument()) { - BufferedImage img = createImage(200, 200, Color.MAGENTA); - PDImageXObject ximg = LosslessFactory.createFromImage(doc, img); - - PdfUtils.addImageToDocument(doc, ximg, "fillPage", false); - - assertEquals(1, doc.getNumberOfPages()); - } - } - - @Test - @DisplayName("overlayImage: everyPage=true overlays all pages") - void overlayImage_shouldOverlayAllPages_whenEveryPageTrue() throws IOException { - CustomPDFDocumentFactory factory = factory(); - - // Create PDF with 2 pages - byte[] basePdf; - try (PDDocument doc = factory.createNewDocument()) { - doc.addPage(new PDPage(PDRectangle.A4)); - doc.addPage(new PDPage(PDRectangle.A4)); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - doc.save(baos); - basePdf = baos.toByteArray(); - } - - // Create image bytes - BufferedImage img = createImage(50, 50, Color.BLACK); - ByteArrayOutputStream pngOut = new ByteArrayOutputStream(); - ImageIO.write(img, "png", pngOut); - - byte[] result = PdfUtils.overlayImage(factory, basePdf, pngOut.toByteArray(), 10, 10, true); - - try (PDDocument out = factory.load(result)) { - assertEquals(2, out.getNumberOfPages(), "Page count remains identical"); - } - } - - /* Helper function: document with text on page1/page2 */ - private static PDDocument createDocWithText(String p1, String p2) throws IOException { - PDDocument doc = new PDDocument(); - - PDPage page1 = new PDPage(PDRectangle.A4); - doc.addPage(page1); - try (PDPageContentStream cs = - new PDPageContentStream(doc, page1, AppendMode.APPEND, true, true)) { - cs.beginText(); - cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); - cs.newLineAtOffset(50, 750); - cs.showText(p1); - cs.endText(); - } - - PDPage page2 = new PDPage(PDRectangle.A4); - doc.addPage(page2); - try (PDPageContentStream cs = - new PDPageContentStream(doc, page2, AppendMode.APPEND, true, true)) { - cs.beginText(); - cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); - cs.newLineAtOffset(50, 750); - cs.showText(p2); - cs.endText(); - } - - return doc; - } - - @Test - @DisplayName("containsTextInFile: pagesToCheck='all' finds text") - void containsTextInFile_allPages_true() throws IOException { - try (PDDocument doc = createDocWithText("alpha", "beta")) { - assertTrue(PdfUtils.containsTextInFile(doc, "beta", "all")); - } - } - - @Test - @DisplayName("containsTextInFile: single page '2' finds text") - void containsTextInFile_singlePage_two_true() throws IOException { - try (PDDocument doc = createDocWithText("alpha", "beta")) { - assertTrue(PdfUtils.containsTextInFile(doc, "beta", "2")); - } - } - - @Test - @DisplayName("containsTextInFile: range '1-1' finds text on page 1") - void containsTextInFile_range_oneToOne_true() throws IOException { - try (PDDocument doc = createDocWithText("findme", "other")) { - assertTrue(PdfUtils.containsTextInFile(doc, "findme", "1-1")); - } - } - - @Test - @DisplayName("containsTextInFile: list '1,2' finds text (whitespace robust)") - void containsTextInFile_list_pages_true() throws IOException { - try (PDDocument doc = createDocWithText("foo", "bar")) { - assertTrue(PdfUtils.containsTextInFile(doc, "bar", " 1 , 2 ")); - } - } - - @Test - @DisplayName("containsTextInFile: text not present -> false") - void containsTextInFile_textNotPresent_false() throws IOException { - try (PDDocument doc = createDocWithText("xxx", "yyy")) { - assertFalse(PdfUtils.containsTextInFile(doc, "zzz", "all")); - } - } - - @Test - @DisplayName("pageSize: different size returns false") - void pageSize_shouldReturnFalse_whenSizeDoesNotMatch() throws IOException { - try (PDDocument doc = new PDDocument()) { - doc.addPage(new PDPage(PDRectangle.A4)); - assertFalse(PdfUtils.pageSize(doc, "600x842")); - } - } - - // ===================== New: convertFromPdf – coverage ===================== - - @Test - @DisplayName("convertFromPdf: singleImage=true creates combined PNG file (readable)") - void convertFromPdf_singleImagePng_combinedReadable() throws Exception { - // Create two-page PDF - byte[] pdfBytes; - PdfMetadataService meta = - new PdfMetadataService(new ApplicationProperties(), "label", false, null); - CustomPDFDocumentFactory factory = new CustomPDFDocumentFactory(meta); - try (PDDocument doc = new PDDocument()) { - doc.addPage(new PDPage(PDRectangle.A4)); - doc.addPage(new PDPage(PDRectangle.A4)); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - doc.save(baos); - pdfBytes = baos.toByteArray(); - } - - byte[] imageBytes = - PdfUtils.convertFromPdf( - factory, pdfBytes, "png", ImageType.RGB, true, 72, "test.pdf", false); - - // Should be readable as a single combined PNG image - BufferedImage img = ImageIO.read(new java.io.ByteArrayInputStream(imageBytes)); - assertNotNull(img, "PNG should be readable"); - assertTrue(img.getWidth() > 0 && img.getHeight() > 0, "Image dimensions > 0"); - } - - @Test - @DisplayName( - "convertFromPdf: singleImage=false returns ZIP with PNG entries (first image readable)") - void convertFromPdf_multiImagePng_firstReadable() throws Exception { - // Create two-page PDF - byte[] pdfBytes; - PdfMetadataService meta = - new PdfMetadataService(new ApplicationProperties(), "label", false, null); - CustomPDFDocumentFactory factory = new CustomPDFDocumentFactory(meta); - try (PDDocument doc = new PDDocument()) { - doc.addPage(new PDPage(PDRectangle.A4)); - doc.addPage(new PDPage(PDRectangle.A4)); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - doc.save(baos); - pdfBytes = baos.toByteArray(); - } - - // Act: singleImage=false -> ZIP with separate images - byte[] zipBytes = - PdfUtils.convertFromPdf( - factory, pdfBytes, "png", ImageType.RGB, false, 72, "test.pdf", false); - - // Assert: open ZIP, read first entry as PNG - try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes))) { - ZipEntry entry = zis.getNextEntry(); - assertNotNull(entry, "ZIP should contain at least one entry"); - - ByteArrayOutputStream imgOut = new ByteArrayOutputStream(); - zis.transferTo(imgOut); - BufferedImage first = ImageIO.read(new ByteArrayInputStream(imgOut.toByteArray())); - - assertNotNull(first, "First PNG entry should be readable"); - assertTrue(first.getWidth() > 0 && first.getHeight() > 0, "Image dimensions > 0"); - } - } - - @Test - @DisplayName("hasText: detects phrase on selected pages ('1', '2', 'all')") - void hasText_shouldDetectPhrase_onSelectedPages() throws Exception { - // Arrange: PDF with 2 pages and text - try (PDDocument doc = new PDDocument()) { - PDPage p1 = new PDPage(PDRectangle.A4); - PDPage p2 = new PDPage(PDRectangle.A4); - doc.addPage(p1); - doc.addPage(p2); - - try (PDPageContentStream cs = - new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) { - cs.beginText(); - cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); - cs.newLineAtOffset(50, 750); - cs.showText("alpha on page 1"); - cs.endText(); - } - try (PDPageContentStream cs = - new PDPageContentStream(doc, p2, AppendMode.APPEND, true, true)) { - cs.beginText(); - cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); - cs.newLineAtOffset(50, 750); - cs.showText("beta on page 2"); - cs.endText(); - } - - assertTrue(PdfUtils.hasText(doc, "1", "alpha"), "Page 1 should contain 'alpha'"); - } - - // For further checks, create new doc with identical content - try (PDDocument doc = new PDDocument()) { - PDPage p1 = new PDPage(PDRectangle.A4); - PDPage p2 = new PDPage(PDRectangle.A4); - doc.addPage(p1); - doc.addPage(p2); - - try (PDPageContentStream cs = - new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) { - cs.beginText(); - cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); - cs.newLineAtOffset(50, 750); - cs.showText("alpha on page 1"); - cs.endText(); - } - try (PDPageContentStream cs = - new PDPageContentStream(doc, p2, AppendMode.APPEND, true, true)) { - cs.beginText(); - cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); - cs.newLineAtOffset(50, 750); - cs.showText("beta on page 2"); - cs.endText(); - } - - assertTrue(PdfUtils.hasText(doc, "2", "beta"), "Page 2 should contain 'beta'"); - } - - // Third doc for 'all' - try (PDDocument doc = new PDDocument()) { - PDPage p1 = new PDPage(PDRectangle.A4); - PDPage p2 = new PDPage(PDRectangle.A4); - doc.addPage(p1); - doc.addPage(p2); - - try (PDPageContentStream cs = - new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) { - cs.beginText(); - cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); - cs.newLineAtOffset(50, 750); - cs.showText("gamma"); - cs.endText(); - } - assertTrue(PdfUtils.hasText(doc, "all", "gamma"), "'all' should find text on page 1"); - } - } - - @Test - @DisplayName("hasTextOnPage: true if page contains phrase, else false") - void hasTextOnPage_shouldReturnTrueOnlyForPagesWithPhrase() throws Exception { - try (PDDocument doc = new PDDocument()) { - PDPage p1 = new PDPage(PDRectangle.A4); - PDPage p2 = new PDPage(PDRectangle.A4); - doc.addPage(p1); - doc.addPage(p2); - - try (PDPageContentStream cs = - new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) { - cs.beginText(); - cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12); - cs.newLineAtOffset(50, 750); - cs.showText("needle"); - cs.endText(); - } - - assertTrue(PdfUtils.hasTextOnPage(p1, "needle")); - assertTrue(!PdfUtils.hasTextOnPage(p2, "needle")); - } - } - - @Test - @DisplayName("hasImages: detects images on selected pages and 'all'") - void hasImages_shouldDetectImages_onSelectedPages() throws Exception { - // Case 1: Page 1 without image (but resources set) -> false - try (PDDocument doc = new PDDocument()) { - PDPage p1 = new PDPage(PDRectangle.A4); - PDPage p2 = new PDPage(PDRectangle.A4); - p1.setResources(new PDResources()); - p2.setResources(new PDResources()); - doc.addPage(p1); - doc.addPage(p2); - - // Image only on page 2 - BufferedImage bi = new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB); - Graphics2D g = bi.createGraphics(); - g.setColor(Color.GREEN); - g.fillRect(0, 0, 20, 20); - g.dispose(); - - PDImageXObject ximg = LosslessFactory.createFromImage(doc, bi); - try (PDPageContentStream cs = - new PDPageContentStream(doc, p2, AppendMode.APPEND, true, true)) { - cs.drawImage(ximg, 50, 700, 20, 20); - } - - assertTrue(!PdfUtils.hasImages(doc, "1"), "Page 1 should have no image"); - } - - // Case 2: Page 2 with image -> true - try (PDDocument doc = new PDDocument()) { - PDPage p1 = new PDPage(PDRectangle.A4); - PDPage p2 = new PDPage(PDRectangle.A4); - p1.setResources(new PDResources()); - p2.setResources(new PDResources()); - doc.addPage(p1); - doc.addPage(p2); - - BufferedImage bi = new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB); - Graphics2D g = bi.createGraphics(); - g.setColor(Color.BLUE); - g.fillRect(0, 0, 20, 20); - g.dispose(); - - PDImageXObject ximg = LosslessFactory.createFromImage(doc, bi); - try (PDPageContentStream cs = - new PDPageContentStream(doc, p2, AppendMode.APPEND, true, true)) { - cs.drawImage(ximg, 50, 700, 20, 20); - } - - assertTrue(PdfUtils.hasImages(doc, "2"), "Page 2 should have an image"); - } - - // Case 3: 'all' detects image - try (PDDocument doc = new PDDocument()) { - PDPage p = new PDPage(PDRectangle.A4); - p.setResources(new PDResources()); - doc.addPage(p); - - BufferedImage bi = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); - PDImageXObject ximg = LosslessFactory.createFromImage(doc, bi); - try (PDPageContentStream cs = - new PDPageContentStream(doc, p, AppendMode.APPEND, true, true)) { - cs.drawImage(ximg, 20, 730, 10, 10); - } - - assertTrue(PdfUtils.hasImages(doc, "all"), "'all' should detect the image"); - } - } - - @Test - @DisplayName("hasImagesOnPage: true if page contains an image, else false") - void hasImagesOnPage_shouldReturnTrueOnlyForPagesWithImage() throws Exception { - try (PDDocument doc = new PDDocument()) { - PDPage p1 = new PDPage(PDRectangle.A4); - PDPage p2 = new PDPage(PDRectangle.A4); - p1.setResources(new PDResources()); - p2.setResources(new PDResources()); - doc.addPage(p1); - doc.addPage(p2); - - BufferedImage bi = new BufferedImage(12, 12, BufferedImage.TYPE_INT_RGB); - Graphics2D g = bi.createGraphics(); - g.setColor(Color.RED); - g.fillRect(0, 0, 12, 12); - g.dispose(); - - PDImageXObject ximg = LosslessFactory.createFromImage(doc, bi); - try (PDPageContentStream cs = - new PDPageContentStream(doc, p1, AppendMode.APPEND, true, true)) { - cs.drawImage(ximg, 40, 720, 12, 12); - } - - assertTrue(PdfUtils.hasImagesOnPage(p1)); - assertTrue(!PdfUtils.hasImagesOnPage(p2)); - } - } - - @Test - @DisplayName("convertFromPdf: singleImage=true with JPG -> no alpha, white background") - void convertFromPdf_singleImageJpg_noAlphaWhiteBackground() throws Exception { - // small 1-page PDF - byte[] pdfBytes; - PdfMetadataService meta = - new PdfMetadataService(new ApplicationProperties(), "label", false, null); - CustomPDFDocumentFactory factory = new CustomPDFDocumentFactory(meta); - try (PDDocument doc = new PDDocument()) { - PDPage p = new PDPage(PDRectangle.A4); - doc.addPage(p); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - doc.save(baos); - pdfBytes = baos.toByteArray(); - } - - byte[] jpgBytes = - PdfUtils.convertFromPdf( - factory, pdfBytes, "jpg", ImageType.RGB, true, 72, "sample.pdf", false); - - BufferedImage img = ImageIO.read(new ByteArrayInputStream(jpgBytes)); - assertNotNull(img, "JPG should be readable"); - - ColorModel cm = img.getColorModel(); - assertFalse(cm.hasAlpha(), "JPG output should have no alpha channel"); - - // JPG background should be white (approximate check) - int rgb = img.getRGB(img.getWidth() / 2, img.getHeight() / 2) & 0x00FFFFFF; - assertEquals(0xFFFFFF, rgb, "Background pixel should be white"); - } -} diff --git a/app/common/src/test/java/stirling/software/common/util/ProcessExecutorTest.java b/app/common/src/test/java/stirling/software/common/util/ProcessExecutorTest.java deleted file mode 100644 index 72db3551de..0000000000 --- a/app/common/src/test/java/stirling/software/common/util/ProcessExecutorTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package stirling.software.common.util; - -import static org.junit.jupiter.api.Assertions.*; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class ProcessExecutorTest { - - private ProcessExecutor processExecutor; - - @BeforeEach - public void setUp() { - // Initialize the ProcessExecutor instance - processExecutor = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE); - } - - @Test - public void testRunCommandWithOutputHandling() throws IOException, InterruptedException { - // Mock the command to execute - List command = new ArrayList<>(); - command.add("java"); - command.add("-version"); - - // Execute the command - ProcessExecutor.ProcessExecutorResult result = - processExecutor.runCommandWithOutputHandling(command); - - // Check the exit code and output messages - assertEquals(0, result.getRc()); - assertNotNull(result.getMessages()); // Check if messages are not null - } - - @Test - public void testRunCommandWithOutputHandling_Error() { - // Test with a command that will fail to execute (non-existent command) - List command = new ArrayList<>(); - command.add("nonexistent-command-that-does-not-exist"); - - // Execute the command and expect an IOException (command not found) - assertThrows( - IOException.class, () -> processExecutor.runCommandWithOutputHandling(command)); - } - - @Test - public void testRunCommandWithOutputHandling_PathTraversal() { - // Test that path traversal is blocked - List command = new ArrayList<>(); - command.add("../../../etc/passwd"); - - // Execute the command and expect an IllegalArgumentException - IllegalArgumentException thrown = - assertThrows( - IllegalArgumentException.class, - () -> processExecutor.runCommandWithOutputHandling(command)); - - // Check the exception message - String errorMessage = thrown.getMessage(); - assertTrue( - errorMessage.contains("path traversal"), - "Unexpected error message: " + errorMessage); - } - - @Test - public void testRunCommandWithOutputHandling_NullByte() { - // Test that null bytes are blocked - List command = new ArrayList<>(); - command.add("test\0command"); - - // Execute the command and expect an IllegalArgumentException - IllegalArgumentException thrown = - assertThrows( - IllegalArgumentException.class, - () -> processExecutor.runCommandWithOutputHandling(command)); - - // Check the exception message - String errorMessage = thrown.getMessage(); - assertTrue( - errorMessage.contains("invalid characters"), - "Unexpected error message: " + errorMessage); - } -} diff --git a/app/common/src/test/java/stirling/software/common/util/PropertyConfigsTest.java b/app/common/src/test/java/stirling/software/common/util/PropertyConfigsTest.java deleted file mode 100644 index a8101add6d..0000000000 --- a/app/common/src/test/java/stirling/software/common/util/PropertyConfigsTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package stirling.software.common.util; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.Test; - -public class PropertyConfigsTest { - - @Test - public void testGetBooleanValue_WithKeys() { - // Define keys and default value - List keys = Arrays.asList("test.key1", "test.key2", "test.key3"); - boolean defaultValue = false; - - // Set property for one of the keys - System.setProperty("test.key2", "true"); - - // Call the method under test - boolean result = PropertyConfigs.getBooleanValue(keys, defaultValue); - - // Verify the result - assertTrue(result); - } - - @Test - public void testGetStringValue_WithKeys() { - // Define keys and default value - List keys = Arrays.asList("test.key1", "test.key2", "test.key3"); - String defaultValue = "default"; - - // Set property for one of the keys - System.setProperty("test.key2", "value"); - - // Call the method under test - String result = PropertyConfigs.getStringValue(keys, defaultValue); - - // Verify the result - assertEquals("value", result); - } - - @Test - public void testGetBooleanValue_WithKey() { - // Define key and default value - String key = "test.key"; - boolean defaultValue = true; - - // Call the method under test - boolean result = PropertyConfigs.getBooleanValue(key, defaultValue); - - // Verify the result - assertTrue(result); - } - - @Test - public void testGetStringValue_WithKey() { - // Define key and default value - String key = "test.key"; - String defaultValue = "default"; - - // Call the method under test - String result = PropertyConfigs.getStringValue(key, defaultValue); - - // Verify the result - assertEquals("default", result); - } -} diff --git a/app/common/src/test/java/stirling/software/common/util/RequestUriUtilsTest.java b/app/common/src/test/java/stirling/software/common/util/RequestUriUtilsTest.java deleted file mode 100644 index 42e0d5dc73..0000000000 --- a/app/common/src/test/java/stirling/software/common/util/RequestUriUtilsTest.java +++ /dev/null @@ -1,340 +0,0 @@ -package stirling.software.common.util; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -public class RequestUriUtilsTest { - - @Test - void testIsStaticResource() { - // Test static resources without context path - assertTrue( - RequestUriUtils.isStaticResource("/css/styles.css"), "CSS files should be static"); - assertTrue(RequestUriUtils.isStaticResource("/js/script.js"), "JS files should be static"); - assertTrue( - RequestUriUtils.isStaticResource("/images/logo.png"), - "Image files should be static"); - assertTrue( - RequestUriUtils.isStaticResource("/public/index.html"), - "Public files should be static"); - assertTrue( - RequestUriUtils.isStaticResource("/pdfjs/pdf.worker.js"), - "PDF.js files should be static"); - assertTrue( - RequestUriUtils.isStaticResource("/pdfium/pdfium.wasm"), - "PDFium wasm should be static"); - assertTrue( - RequestUriUtils.isStaticResource("/api/v1/info/status"), - "API status should be static"); - assertTrue( - RequestUriUtils.isStaticResource("/some-path/icon.svg"), - "SVG files should be static"); - assertTrue(RequestUriUtils.isStaticResource("/login"), "Login page should be static"); - assertTrue(RequestUriUtils.isStaticResource("/error"), "Error page should be static"); - - // Test non-static resources - assertFalse( - RequestUriUtils.isStaticResource("/api/v1/users"), - "API users should not be static"); - assertFalse( - RequestUriUtils.isStaticResource("/api/v1/orders"), - "API orders should not be static"); - assertFalse(RequestUriUtils.isStaticResource("/"), "Root path should not be static"); - assertFalse( - RequestUriUtils.isStaticResource("/register"), - "Register page should not be static"); - assertFalse( - RequestUriUtils.isStaticResource("/api/v1/products"), - "API products should not be static"); - } - - @Test - void testIsFrontendRoute() { - assertTrue( - RequestUriUtils.isFrontendRoute("", "/"), "Root path should be a frontend route"); - assertTrue( - RequestUriUtils.isFrontendRoute("", "/app/dashboard"), - "React routes without extensions should be frontend routes"); - assertFalse( - RequestUriUtils.isFrontendRoute("", "/api/v1/users"), - "API routes should not be frontend routes"); - assertFalse( - RequestUriUtils.isFrontendRoute("", "/register"), - "Register should not be treated as a frontend route"); - assertFalse( - RequestUriUtils.isFrontendRoute("", "/pipeline/jobs"), - "Pipeline should not be treated as a frontend route"); - assertFalse( - RequestUriUtils.isFrontendRoute("", "/files/download"), - "Files path should not be treated as a frontend route"); - } - - @Test - void testIsStaticResourceWithContextPath() { - String contextPath = "/myapp"; - - // Test static resources with context path - assertTrue( - RequestUriUtils.isStaticResource(contextPath, contextPath + "/css/styles.css"), - "CSS with context path should be static"); - assertTrue( - RequestUriUtils.isStaticResource(contextPath, contextPath + "/js/script.js"), - "JS with context path should be static"); - assertTrue( - RequestUriUtils.isStaticResource(contextPath, contextPath + "/images/logo.png"), - "Images with context path should be static"); - assertTrue( - RequestUriUtils.isStaticResource(contextPath, contextPath + "/login"), - "Login with context path should be static"); - - // Test non-static resources with context path - assertFalse( - RequestUriUtils.isStaticResource(contextPath, contextPath + "/api/v1/users"), - "API users with context path should not be static"); - assertFalse( - RequestUriUtils.isStaticResource(contextPath, "/"), - "Root path with context path should not be static"); - } - - @ParameterizedTest - @ValueSource( - strings = { - "robots.txt", - "/favicon.ico", - "/icon.svg", - "/image.png", - "/locales/en/translation.toml", - "/site.webmanifest", - "/app/logo.svg", - "/downloads/document.png", - "/assets/brand.ico", - "/any/path/with/image.svg", - "/deep/nested/folder/icon.png", - "/pdfium/pdfium.wasm" - }) - void testIsStaticResourceWithFileExtensions(String path) { - assertTrue( - RequestUriUtils.isStaticResource(path), - "Files with specific extensions should be static regardless of path"); - } - - @Test - void testIsTrackableResource() { - // Test non-trackable resources (returns false) - assertFalse( - RequestUriUtils.isTrackableResource("/js/script.js"), - "JS files should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/v1/api-docs"), - "API docs should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("robots.txt"), - "robots.txt should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/images/logo.png"), - "Images should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/styles.css"), - "CSS files should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/script.js.map"), - "Map files should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/icon.svg"), - "SVG files should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/popularity.txt"), - "Popularity file should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/script.js"), - "JS files should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/pdfium/pdfium.wasm"), - "PDFium wasm should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/swagger/index.html"), - "Swagger files should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/api/v1/info/status"), - "API info should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/site.webmanifest"), - "Webmanifest should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/fonts/font.woff"), - "Fonts should not be trackable"); - assertFalse( - RequestUriUtils.isTrackableResource("/pdfjs/viewer.js"), - "PDF.js files should not be trackable"); - - // Test trackable resources (returns true) - assertTrue(RequestUriUtils.isTrackableResource("/login"), "Login page should be trackable"); - assertTrue( - RequestUriUtils.isTrackableResource("/register"), - "Register page should be trackable"); - assertTrue( - RequestUriUtils.isTrackableResource("/api/v1/users"), - "API users should be trackable"); - assertTrue(RequestUriUtils.isTrackableResource("/"), "Root path should be trackable"); - assertTrue( - RequestUriUtils.isTrackableResource("/some-other-path"), - "Other paths should be trackable"); - } - - @Test - void testIsTrackableResourceWithContextPath() { - String contextPath = "/myapp"; - - // Test with context path - assertFalse( - RequestUriUtils.isTrackableResource(contextPath, "/js/script.js"), - "JS files should not be trackable with context path"); - assertTrue( - RequestUriUtils.isTrackableResource(contextPath, "/login"), - "Login page should be trackable with context path"); - - // Additional tests with context path - assertFalse( - RequestUriUtils.isTrackableResource(contextPath, "/fonts/custom.woff"), - "Font files should not be trackable with context path"); - assertFalse( - RequestUriUtils.isTrackableResource(contextPath, "/images/header.png"), - "Images should not be trackable with context path"); - assertFalse( - RequestUriUtils.isTrackableResource(contextPath, "/swagger/ui.html"), - "Swagger UI should not be trackable with context path"); - assertTrue( - RequestUriUtils.isTrackableResource(contextPath, "/account/profile"), - "Account page should be trackable with context path"); - assertTrue( - RequestUriUtils.isTrackableResource(contextPath, "/pdf/view"), - "PDF view page should be trackable with context path"); - } - - @ParameterizedTest - @ValueSource( - strings = { - "/js/util.js", - "/v1/api-docs/swagger.json", - "/robots.txt", - "/images/header/logo.png", - "/styles/theme.css", - "/build/app.js.map", - "/assets/icon.svg", - "/data/popularity.txt", - "/bundle.js", - "/api/swagger-ui.html", - "/api/v1/info/health", - "/site.webmanifest", - "/fonts/roboto.woff", - "/pdfjs/viewer.js", - "/pdfium/pdfium.wasm" - }) - void testNonTrackableResources(String path) { - assertFalse( - RequestUriUtils.isTrackableResource(path), - "Resources matching patterns should not be trackable: " + path); - } - - @ParameterizedTest - @ValueSource( - strings = { - "/", - "/home", - "/login", - "/register", - "/pdf/merge", - "/pdf/split", - "/api/v1/users/1", - "/api/v1/documents/process", - "/settings", - "/account/profile", - "/dashboard", - "/help", - "/about" - }) - void testTrackableResources(String path) { - assertTrue( - RequestUriUtils.isTrackableResource(path), - "App routes should be trackable: " + path); - } - - @Test - void testEdgeCases() { - // Test with empty strings - assertFalse(RequestUriUtils.isStaticResource("", ""), "Empty path should not be static"); - assertTrue(RequestUriUtils.isTrackableResource("", ""), "Empty path should be trackable"); - - // Test with null-like behavior (would actually throw NPE in real code) - // These are not actual null tests but shows handling of odd cases - assertFalse(RequestUriUtils.isStaticResource("null"), "String 'null' should not be static"); - - // Test String "null" as a path - boolean isTrackable = RequestUriUtils.isTrackableResource("null"); - assertTrue(isTrackable, "String 'null' should be trackable"); - - // Mixed case extensions test - note that Java's endsWith() is case-sensitive - // We'll check actual behavior and document it rather than asserting - - // Always test the lowercase versions which should definitely work - assertTrue( - RequestUriUtils.isStaticResource("/logo.png"), "PNG (lowercase) should be static"); - assertTrue( - RequestUriUtils.isStaticResource("/icon.svg"), "SVG (lowercase) should be static"); - - // Path with query parameters - assertFalse( - RequestUriUtils.isStaticResource("/api/users?page=1"), - "Path with query params should respect base path"); - assertTrue( - RequestUriUtils.isStaticResource("/images/logo.png?v=123"), - "Static resource with query params should still be static"); - - // Paths with fragments - assertTrue( - RequestUriUtils.isStaticResource("/css/styles.css#section1"), - "CSS with fragment should be static"); - - // Multiple dots in filename - assertTrue( - RequestUriUtils.isStaticResource("/js/jquery.min.js"), - "JS with multiple dots should be static"); - - // Special characters in path - assertTrue( - RequestUriUtils.isStaticResource("/images/user's-photo.png"), - "Path with special chars should be handled correctly"); - } - - @Test - void testComplexPaths() { - // Test complex static resource paths - assertTrue( - RequestUriUtils.isStaticResource("/css/theme/dark/styles.css"), - "Nested CSS should be static"); - assertTrue( - RequestUriUtils.isStaticResource("/fonts/open-sans/bold/font.woff"), - "Nested font should be static"); - assertTrue( - RequestUriUtils.isStaticResource("/js/vendor/jquery/3.5.1/jquery.min.js"), - "Versioned JS should be static"); - - // Test complex paths with context - String contextPath = "/app"; - assertTrue( - RequestUriUtils.isStaticResource( - contextPath, contextPath + "/css/theme/dark/styles.css"), - "Nested CSS with context should be static"); - - // Test boundary cases for isTrackableResource - assertFalse( - RequestUriUtils.isTrackableResource("/js-framework/components"), - "Path starting with js- should not be treated as JS resource"); - assertFalse( - RequestUriUtils.isTrackableResource("/fonts-selection"), - "Path starting with fonts- should not be treated as font resource"); - } -} diff --git a/app/common/src/test/java/stirling/software/common/util/UrlUtilsTest.java b/app/common/src/test/java/stirling/software/common/util/UrlUtilsTest.java deleted file mode 100644 index 1e497168aa..0000000000 --- a/app/common/src/test/java/stirling/software/common/util/UrlUtilsTest.java +++ /dev/null @@ -1,276 +0,0 @@ -package stirling.software.common.util; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.net.ServerSocket; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import jakarta.servlet.http.HttpServletRequest; - -@ExtendWith(MockitoExtension.class) -class UrlUtilsTest { - - @Mock private HttpServletRequest request; - - @Test - void testGetOrigin() { - // Arrange - when(request.getScheme()).thenReturn("http"); - when(request.getServerName()).thenReturn("localhost"); - when(request.getServerPort()).thenReturn(8080); - when(request.getContextPath()).thenReturn("/myapp"); - - // Act - String origin = UrlUtils.getOrigin(request); - - // Assert - assertEquals( - "http://localhost:8080/myapp", origin, "Origin URL should be correctly formatted"); - } - - @Test - void testGetOriginWithHttps() { - // Arrange - when(request.getScheme()).thenReturn("https"); - when(request.getServerName()).thenReturn("example.com"); - when(request.getServerPort()).thenReturn(443); - when(request.getContextPath()).thenReturn(""); - - // Act - String origin = UrlUtils.getOrigin(request); - - // Assert - assertEquals( - "https://example.com:443", - origin, - "HTTPS origin URL should be correctly formatted"); - } - - @Test - void testGetOriginWithEmptyContextPath() { - // Arrange - when(request.getScheme()).thenReturn("http"); - when(request.getServerName()).thenReturn("localhost"); - when(request.getServerPort()).thenReturn(8080); - when(request.getContextPath()).thenReturn(""); - - // Act - String origin = UrlUtils.getOrigin(request); - - // Assert - assertEquals( - "http://localhost:8080", - origin, - "Origin URL with empty context path should be correct"); - } - - @Test - void testGetOriginWithSpecialCharacters() { - // Arrange - Test with server name containing special characters - when(request.getScheme()).thenReturn("https"); - when(request.getServerName()).thenReturn("internal-server.example-domain.com"); - when(request.getServerPort()).thenReturn(8443); - when(request.getContextPath()).thenReturn("/app-v1.2"); - - // Act - String origin = UrlUtils.getOrigin(request); - - // Assert - assertEquals( - "https://internal-server.example-domain.com:8443/app-v1.2", - origin, - "Origin URL with special characters should be correctly formatted"); - } - - @Test - void testGetOriginWithIPv4Address() { - // Arrange - when(request.getScheme()).thenReturn("http"); - when(request.getServerName()).thenReturn("192.168.1.100"); - when(request.getServerPort()).thenReturn(8080); - when(request.getContextPath()).thenReturn("/app"); - - // Act - String origin = UrlUtils.getOrigin(request); - - // Assert - assertEquals( - "http://192.168.1.100:8080/app", - origin, - "Origin URL with IPv4 address should be correctly formatted"); - } - - @Test - void testGetOriginWithNonStandardPort() { - // Arrange - when(request.getScheme()).thenReturn("https"); - when(request.getServerName()).thenReturn("example.org"); - when(request.getServerPort()).thenReturn(8443); - when(request.getContextPath()).thenReturn("/api"); - - // Act - String origin = UrlUtils.getOrigin(request); - - // Assert - assertEquals( - "https://example.org:8443/api", - origin, - "Origin URL with non-standard port should be correctly formatted"); - } - - @Test - void testIsPortAvailable() { - // We'll use a real server socket for this test - ServerSocket socket = null; - int port = 12345; // Choose a port unlikely to be in use - - try { - // First check the port is available - boolean initialAvailability = UrlUtils.isPortAvailable(port); - - // Then occupy the port - socket = new ServerSocket(port); - - // Now check the port is no longer available - boolean afterSocketCreation = UrlUtils.isPortAvailable(port); - - // Assert - assertTrue(initialAvailability, "Port should be available initially"); - assertFalse( - afterSocketCreation, "Port should not be available after socket is created"); - - } catch (IOException e) { - // This might happen if the port is already in use by another process - // We'll just verify the behavior of isPortAvailable matches what we expect - assertFalse( - UrlUtils.isPortAvailable(port), - "Port should not be available if exception is thrown"); - } finally { - if (socket != null && !socket.isClosed()) { - try { - socket.close(); - } catch (IOException e) { - // Ignore cleanup exceptions - } - } - } - } - - @Test - void testFindAvailablePort() { - // We'll create a socket on a port and ensure findAvailablePort returns a different port - ServerSocket socket = null; - int startPort = 12346; // Choose a port unlikely to be in use - - try { - // Occupy the start port - socket = new ServerSocket(startPort); - - // Find an available port - String availablePort = UrlUtils.findAvailablePort(startPort); - - // Assert the returned port is not the occupied one - assertNotEquals( - String.valueOf(startPort), - availablePort, - "findAvailablePort should not return an occupied port"); - - // Verify the returned port is actually available - int portNumber = Integer.parseInt(availablePort); - - // Close our test socket before checking the found port - socket.close(); - socket = null; - - // The port should now be available - assertTrue( - UrlUtils.isPortAvailable(portNumber), - "The port returned by findAvailablePort should be available"); - - } catch (IOException e) { - // If we can't create the socket, skip this assertion - } finally { - if (socket != null && !socket.isClosed()) { - try { - socket.close(); - } catch (IOException e) { - // Ignore cleanup exceptions - } - } - } - } - - @Test - void testFindAvailablePortWithAvailableStartPort() { - // Find an available port without occupying any - int startPort = 23456; // Choose a different unlikely-to-be-used port - - // Make sure the port is available first - if (UrlUtils.isPortAvailable(startPort)) { - // Find an available port - String availablePort = UrlUtils.findAvailablePort(startPort); - - // Assert the returned port is the start port since it's available - assertEquals( - String.valueOf(startPort), - availablePort, - "findAvailablePort should return the start port if it's available"); - } - } - - @Test - void testFindAvailablePortWithSequentialUsedPorts() { - // This test checks that findAvailablePort correctly skips multiple occupied ports - ServerSocket socket1 = null; - ServerSocket socket2 = null; - int startPort = 34567; // Another unlikely-to-be-used port - - try { - // First verify the port is available - if (!UrlUtils.isPortAvailable(startPort)) { - return; - } - - // Occupy two sequential ports - socket1 = new ServerSocket(startPort); - socket2 = new ServerSocket(startPort + 1); - - // Find an available port starting from our occupied range - String availablePort = UrlUtils.findAvailablePort(startPort); - int foundPort = Integer.parseInt(availablePort); - - // Should have skipped the two occupied ports - assertTrue( - foundPort >= startPort + 2, - "findAvailablePort should skip sequential occupied ports"); - - // Verify the found port is actually available - try (ServerSocket testSocket = new ServerSocket(foundPort)) { - assertTrue(testSocket.isBound(), "The found port should be bindable"); - } - - } catch (IOException e) { - // Skip test if we encounter IO exceptions - } finally { - // Clean up resources - try { - if (socket1 != null && !socket1.isClosed()) socket1.close(); - if (socket2 != null && !socket2.isClosed()) socket2.close(); - } catch (IOException e) { - // Ignore cleanup exceptions - } - } - } - - @Test - void testIsPortAvailableWithPrivilegedPorts() { - // Skip tests for privileged ports as they typically require root access - // and results are environment-dependent - } -} diff --git a/app/common/src/test/java/stirling/software/common/util/WebResponseUtilsTest.java b/app/common/src/test/java/stirling/software/common/util/WebResponseUtilsTest.java deleted file mode 100644 index 86be0c8123..0000000000 --- a/app/common/src/test/java/stirling/software/common/util/WebResponseUtilsTest.java +++ /dev/null @@ -1,115 +0,0 @@ -package stirling.software.common.util; - -import static org.junit.jupiter.api.Assertions.*; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import org.apache.pdfbox.pdmodel.PDDocument; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockMultipartFile; - -public class WebResponseUtilsTest { - - @Test - public void testBoasToWebResponse() { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - baos.write("Sample PDF content".getBytes()); - String docName = "sample.pdf"; - - ResponseEntity responseEntity = - WebResponseUtils.baosToWebResponse(baos, docName); - - assertNotNull(responseEntity); - assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); - assertNotNull(responseEntity.getBody()); - - HttpHeaders headers = responseEntity.getHeaders(); - assertNotNull(headers); - assertEquals(MediaType.APPLICATION_PDF, headers.getContentType()); - assertNotNull(headers.getContentDisposition()); - // assertEquals("attachment; filename=\"sample.pdf\"", - // headers.getContentDisposition().toString()); - - } catch (IOException e) { - fail("Exception thrown: " + e.getMessage()); - } - } - - @Test - public void testMultiPartFileToWebResponse() { - try { - byte[] fileContent = "Sample file content".getBytes(); - MockMultipartFile file = - new MockMultipartFile( - "file", "sample.txt", MediaType.TEXT_PLAIN_VALUE, fileContent); - - ResponseEntity responseEntity = - WebResponseUtils.multiPartFileToWebResponse(file); - - assertNotNull(responseEntity); - assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); - assertNotNull(responseEntity.getBody()); - - HttpHeaders headers = responseEntity.getHeaders(); - assertNotNull(headers); - assertEquals(MediaType.TEXT_PLAIN, headers.getContentType()); - assertNotNull(headers.getContentDisposition()); - - } catch (IOException e) { - fail("Exception thrown: " + e.getMessage()); - } - } - - @Test - public void testBytesToWebResponse() { - try { - byte[] bytes = "Sample bytes".getBytes(); - String docName = "sample.txt"; - MediaType mediaType = MediaType.TEXT_PLAIN; - - ResponseEntity responseEntity = - WebResponseUtils.bytesToWebResponse(bytes, docName, mediaType); - - assertNotNull(responseEntity); - assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); - assertNotNull(responseEntity.getBody()); - - HttpHeaders headers = responseEntity.getHeaders(); - assertNotNull(headers); - assertEquals(MediaType.TEXT_PLAIN, headers.getContentType()); - assertNotNull(headers.getContentDisposition()); - - } catch (IOException e) { - fail("Exception thrown: " + e.getMessage()); - } - } - - @Test - public void testPdfDocToWebResponse() { - try (PDDocument document = new PDDocument()) { - document.addPage(new org.apache.pdfbox.pdmodel.PDPage()); - String docName = "sample.pdf"; - - ResponseEntity responseEntity = - WebResponseUtils.pdfDocToWebResponse(document, docName); - - assertNotNull(responseEntity); - assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); - assertNotNull(responseEntity.getBody()); - - HttpHeaders headers = responseEntity.getHeaders(); - assertNotNull(headers); - assertEquals(MediaType.APPLICATION_PDF, headers.getContentType()); - assertNotNull(headers.getContentDisposition()); - - } catch (IOException e) { - fail("Exception thrown: " + e.getMessage()); - } - } -} diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java deleted file mode 100644 index edb66a87b6..0000000000 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/PdfImageRemovalController.java +++ /dev/null @@ -1,80 +0,0 @@ -package stirling.software.SPDF.controller.api; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import org.apache.pdfbox.pdmodel.PDDocument; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ModelAttribute; - -import io.swagger.v3.oas.annotations.Operation; - -import lombok.RequiredArgsConstructor; - -import stirling.software.SPDF.config.swagger.StandardPdfResponse; -import stirling.software.SPDF.service.PdfImageRemovalService; -import stirling.software.common.annotations.AutoJobPostMapping; -import stirling.software.common.annotations.api.GeneralApi; -import stirling.software.common.model.api.PDFFile; -import stirling.software.common.service.CustomPDFDocumentFactory; -import stirling.software.common.util.GeneralUtils; -import stirling.software.common.util.WebResponseUtils; - -/** - * Controller class for handling PDF image removal requests. Provides an endpoint to remove images - * from a PDF file to reduce its size. - */ -@GeneralApi -@RequiredArgsConstructor -public class PdfImageRemovalController { - - // Service for removing images from PDFs - private final PdfImageRemovalService pdfImageRemovalService; - - private final CustomPDFDocumentFactory pdfDocumentFactory; - - /** - * Endpoint to remove images from a PDF file. - * - *

This method processes the uploaded PDF file, removes all images, and returns the modified - * PDF file with a new name indicating that images were removed. - * - * @param file The PDF file with images to be removed. - * @return ResponseEntity containing the modified PDF file as byte array with appropriate - * content type and filename. - * @throws IOException If an error occurs while processing the PDF file. - */ - @AutoJobPostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, value = "/remove-image-pdf") - @StandardPdfResponse - @Operation( - summary = "Remove images from file to reduce the file size.", - description = - "This endpoint remove images from file to reduce the file size.Input:PDF" - + " Output:PDF Type:SISO") - public ResponseEntity removeImages(@ModelAttribute PDFFile file) throws IOException { - // Load the PDF document with proper resource management - try (PDDocument document = pdfDocumentFactory.load(file)) { - - // Remove images from the PDF document using the service - try (PDDocument modifiedDocument = - pdfImageRemovalService.removeImagesFromPdf(document)) { - - // Create a ByteArrayOutputStream to hold the modified PDF data - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - // Save the modified PDF document to the output stream - modifiedDocument.save(outputStream); - - // Generate a new filename for the modified PDF - String mergedFileName = - GeneralUtils.generateFilename( - file.getFileInput().getOriginalFilename(), "_images_removed.pdf"); - - // Convert the byte array to a web response and return it - return WebResponseUtils.bytesToWebResponse( - outputStream.toByteArray(), mergedFileName); - } - } - } -} diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java index dbf64e51c7..9ad66a2dd6 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java @@ -150,28 +150,6 @@ public class RearrangePagesPDFController { return newPageOrder; } - /** - * Rearrange pages in a PDF file by merging odd and even pages. The first half of the pages will - * be the odd pages, and the second half will be the even pages as input.
- * This method is visible for testing purposes only. - * - * @param totalPages Total number of pages in the PDF file. - * @return List of page numbers in the new order. The first page is 0. - */ - List oddEvenMerge(int totalPages) { - List newPageOrderZeroBased = new ArrayList<>(); - int numberOfOddPages = (totalPages + 1) / 2; - - for (int oneBasedIndex = 1; oneBasedIndex < (numberOfOddPages + 1); oneBasedIndex++) { - newPageOrderZeroBased.add((oneBasedIndex - 1)); - if (numberOfOddPages + oneBasedIndex <= totalPages) { - newPageOrderZeroBased.add((numberOfOddPages + oneBasedIndex - 1)); - } - } - - return newPageOrderZeroBased; - } - private List duplicate(int totalPages, String pageOrder) { List newPageOrder = new ArrayList<>(); int duplicateCount; @@ -220,7 +198,6 @@ public class RearrangePagesPDFController { case BOOKLET_SORT -> bookletSort(totalPages); case SIDE_STITCH_BOOKLET_SORT -> sideStitchBooklet(totalPages); case ODD_EVEN_SPLIT -> oddEvenSplit(totalPages); - case ODD_EVEN_MERGE -> oddEvenMerge(totalPages); case REMOVE_FIRST -> removeFirst(totalPages); case REMOVE_LAST -> removeLast(totalPages); case REMOVE_FIRST_AND_LAST -> removeFirstAndLast(totalPages); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java index 8f5572dfe1..15455f7b38 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImagesController.java @@ -1,20 +1,14 @@ package stirling.software.SPDF.controller.api.misc; -import java.awt.*; +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.IOException; import java.nio.file.Files; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.zip.Deflater; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -43,7 +37,6 @@ import stirling.software.common.annotations.api.MiscApi; import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.util.ExceptionUtils; import stirling.software.common.util.GeneralUtils; -import stirling.software.common.util.ImageProcessingUtils; import stirling.software.common.util.TempFile; import stirling.software.common.util.TempFileManager; import stirling.software.common.util.WebResponseUtils; @@ -65,203 +58,97 @@ public class ExtractImagesController { + " file. Users can specify the output image format. Input:PDF" + " Output:IMAGE/ZIP Type:SIMO") public ResponseEntity extractImages( - @ModelAttribute PDFExtractImagesRequest request) - throws IOException, InterruptedException, ExecutionException { + @ModelAttribute PDFExtractImagesRequest request) throws IOException { MultipartFile file = request.getFileInput(); - String format = request.getFormat(); - boolean allowDuplicates = Boolean.TRUE.equals(request.getAllowDuplicates()); + String imageFormat = request.getFormat(); - String filename = GeneralUtils.removeExtension(file.getOriginalFilename()); - Set processedImages = new HashSet<>(); + String baseFilename = GeneralUtils.removeExtension(file.getOriginalFilename()); + Set processedImageHashes = new HashSet<>(); - TempFile zipTempFile = new TempFile(tempFileManager, ".zip"); - try (ZipOutputStream zos = - new ZipOutputStream(Files.newOutputStream(zipTempFile.getPath())); - PDDocument document = pdfDocumentFactory.load(file)) { + TempFile zipFile = new TempFile(tempFileManager, ".zip"); + try (ZipOutputStream zipStream = new ZipOutputStream(Files.newOutputStream(zipFile.getPath())); + PDDocument pdfDoc = pdfDocumentFactory.load(file)) { - // Set compression level - zos.setLevel(Deflater.BEST_COMPRESSION); + zipStream.setLevel(Deflater.BEST_COMPRESSION); - // Determine if multithreading should be used based on PDF size or number of pages - boolean useMultithreading = shouldUseMultithreading(file, document); - - if (useMultithreading) { - ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); - Set> futures = new HashSet<>(); - - try { - int pageCount = document.getPages().getCount(); - log.debug("Document reports {} pages", pageCount); - - int consecutiveFailures = 0; - - for (int pgNum = 0; pgNum < pageCount; pgNum++) { - try { - PDPage page = document.getPage(pgNum); - consecutiveFailures = 0; // Reset on success - final int currentPageNum = - pgNum + 1; // Convert to 1-based page numbering - Future future = - executor.submit( - () -> { - try { - // Call the image extraction method for each - // page - extractImagesFromPage( - page, - format, - filename, - currentPageNum, - processedImages, - zos, - allowDuplicates); - } catch (Exception e) { - // Log the error and continue processing other - // pages - ExceptionUtils.logException( - "image extraction from page " - + currentPageNum, - e); - } - - return null; // Callable requires a return type - }); - - // Add the Future object to the list to track completion - futures.add(future); - } catch (Exception e) { - consecutiveFailures++; - ExceptionUtils.logException("page access for page " + (pgNum + 1), e); - - if (consecutiveFailures >= 3) { - log.warn("Stopping page iteration after 3 consecutive failures"); - break; - } - } - } - } catch (Exception e) { - ExceptionUtils.logException("page count determination", e); - throw e; - } - - // Wait for all tasks to complete - for (Future future : futures) { - future.get(); - } - - // Close executor service - executor.shutdown(); - } else { - // Single-threaded extraction - for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) { - PDPage page = document.getPage(pgNum); - extractImagesFromPage( - page, - format, - filename, - pgNum + 1, - processedImages, - zos, - allowDuplicates); - } + int totalPages = pdfDoc.getNumberOfPages(); + for (int pageIndex = 0; pageIndex < totalPages; pageIndex++) { + PDPage currentPage = pdfDoc.getPage(pageIndex); + extractAndAddImagesToZip( + currentPage, imageFormat, baseFilename, pageIndex + 1, + processedImageHashes, zipStream); } - // document and zos closed by try-with-resources } catch (Exception e) { - zipTempFile.close(); + zipFile.close(); throw e; } return WebResponseUtils.zipFileToWebResponse( - zipTempFile, filename + "_extracted-images.zip"); + zipFile, baseFilename + "_extracted-images.zip"); } - private boolean shouldUseMultithreading(MultipartFile file, PDDocument document) { - // Criteria: Use multithreading if file size > 10MB or number of pages > 20 - long fileSizeInMB = file.getSize() / (1024 * 1024); - int numberOfPages = document.getPages().getCount(); - return fileSizeInMB > 10 || numberOfPages > 20; - } - - private void extractImagesFromPage( + private void extractAndAddImagesToZip( PDPage page, - String format, - String filename, - int pageNum, - Set processedImages, - ZipOutputStream zos, - boolean allowDuplicates) + String imageFormat, + String baseFilename, + int pageNumber, + Set seenImageHashes, + ZipOutputStream zipOutput) throws IOException { - MessageDigest md; - try { - md = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - log.error("MD5 algorithm not available for extractImages hash.", e); - return; - } if (page.getResources() == null || page.getResources().getXObjectNames() == null) { return; } - int count = 1; - for (COSName name : page.getResources().getXObjectNames()) { + + int imageCount = 1; + for (COSName resourceName : page.getResources().getXObjectNames()) { + if (!page.getResources().isImageXObject(resourceName)) { + continue; + } + try { - if (page.getResources().isImageXObject(name)) { - PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name); - if (!allowDuplicates) { - byte[] data = ImageProcessingUtils.getImageData(image.getImage()); - byte[] imageHash = md.digest(data); - synchronized (processedImages) { - if (processedImages.stream() - .anyMatch(hash -> Arrays.equals(hash, imageHash))) { - continue; // Skip already processed images - } - processedImages.add(imageHash); - } - } + PDImageXObject imageObject = + (PDImageXObject) page.getResources().getXObject(resourceName); + int imageHashCode = imageObject.hashCode(); - RenderedImage renderedImage = image.getImage(); - - // Convert to standard RGB colorspace if needed - BufferedImage bufferedImage = convertToRGB(renderedImage, format); - - // Encode image outside the lock to allow parallel encoding across threads - String imageName = filename + "_page_" + pageNum + "_" + count++ + "." + format; - ByteArrayOutputStream imageBaos = new ByteArrayOutputStream(); - ImageIO.write(bufferedImage, format, imageBaos); - byte[] imageData = imageBaos.toByteArray(); - - // Write encoded bytes to zip under lock (ZipOutputStream requires - // serialization) - synchronized (zos) { - zos.putNextEntry(new ZipEntry(imageName)); - zos.write(imageData); - zos.closeEntry(); - } + if (seenImageHashes.contains(imageHashCode)) { + continue; } + seenImageHashes.add(imageHashCode); + + RenderedImage sourceImage = imageObject.getImage(); + BufferedImage convertedImage = convertImageToFormat(sourceImage, imageFormat); + + String imagePath = + baseFilename + "_page_" + pageNumber + "_" + imageCount++ + "." + + imageFormat; + ByteArrayOutputStream imageBuffer = new ByteArrayOutputStream(); + ImageIO.write(convertedImage, imageFormat, imageBuffer); + + zipOutput.putNextEntry(new ZipEntry(imagePath)); + zipOutput.write(imageBuffer.toByteArray()); + zipOutput.closeEntry(); + } catch (IOException e) { - ExceptionUtils.logException("image extraction", e); + ExceptionUtils.logException("image extraction failed", e); throw ExceptionUtils.handlePdfException(e, "during image extraction"); } } } - private BufferedImage convertToRGB(RenderedImage renderedImage, String format) { - int width = renderedImage.getWidth(); - int height = renderedImage.getHeight(); - BufferedImage rgbImage; + private BufferedImage convertImageToFormat(RenderedImage source, String format) { + int width = source.getWidth(); + int height = source.getHeight(); + int imageType = BufferedImage.TYPE_INT_RGB; if ("png".equalsIgnoreCase(format)) { - rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - } else if ("jpeg".equalsIgnoreCase(format) || "jpg".equalsIgnoreCase(format)) { - rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - } else if ("gif".equalsIgnoreCase(format)) { - rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED); - } else { - rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + imageType = BufferedImage.TYPE_INT_ARGB; } - Graphics2D g = rgbImage.createGraphics(); - g.drawImage((Image) renderedImage, 0, 0, null); - g.dispose(); - return rgbImage; + BufferedImage result = new BufferedImage(width, height, imageType); + Graphics2D graphics = result.createGraphics(); + graphics.drawImage((Image) source, 0, 0, null); + graphics.dispose(); + + return result; } } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java index db3d6f40b4..dd46462282 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/pipeline/PipelineDirectoryProcessor.java @@ -36,7 +36,6 @@ import stirling.software.SPDF.model.PipelineResult; import stirling.software.SPDF.service.ApiDocService; import stirling.software.common.configuration.RuntimePathConfig; import stirling.software.common.service.PostHogService; -import stirling.software.common.util.FileMonitor; import tools.jackson.databind.ObjectMapper; @@ -50,7 +49,6 @@ public class PipelineDirectoryProcessor { private final ObjectMapper objectMapper; private final ApiDocService apiDocService; private final PipelineProcessor processor; - private final FileMonitor fileMonitor; private final PostHogService postHogService; private final List watchedFoldersDirs; private final String finishedFoldersDir; @@ -63,13 +61,11 @@ public class PipelineDirectoryProcessor { ObjectMapper objectMapper, ApiDocService apiDocService, PipelineProcessor processor, - FileMonitor fileMonitor, PostHogService postHogService, RuntimePathConfig runtimePathConfig) { this.objectMapper = objectMapper; this.apiDocService = apiDocService; this.processor = processor; - this.fileMonitor = fileMonitor; this.postHogService = postHogService; this.watchedFoldersDirs = runtimePathConfig.getPipelineWatchedFoldersPaths(); this.finishedFoldersDir = runtimePathConfig.getPipelineFinishedFoldersPath(); @@ -274,18 +270,7 @@ public class PipelineDirectoryProcessor { return isAllowed; }) .map(Path::toAbsolutePath) - .filter( - path -> { - boolean isReady = - fileMonitor.isFileReadyForProcessing(path); - if (!isReady) { - log.info( - "File not ready for processing (locked/created" - + " last 5s): {}", - path); - } - return isReady; - }) + .filter(path -> true) .map(Path::toFile) .toArray(File[]::new); log.info( diff --git a/app/core/src/main/java/stirling/software/SPDF/model/SortTypes.java b/app/core/src/main/java/stirling/software/SPDF/model/SortTypes.java index 14d12b5a92..6fe81c2203 100644 --- a/app/core/src/main/java/stirling/software/SPDF/model/SortTypes.java +++ b/app/core/src/main/java/stirling/software/SPDF/model/SortTypes.java @@ -7,7 +7,6 @@ public enum SortTypes { BOOKLET_SORT, SIDE_STITCH_BOOKLET_SORT, ODD_EVEN_SPLIT, - ODD_EVEN_MERGE, REMOVE_FIRST, REMOVE_LAST, REMOVE_FIRST_AND_LAST, diff --git a/app/core/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java b/app/core/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java index 050dc1ebae..d3f79ce56b 100644 --- a/app/core/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java +++ b/app/core/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java @@ -22,7 +22,6 @@ public class RearrangePagesRequest extends PDFWithPageNums { + "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). " + "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n" + "ODD_EVEN_SPLIT: Splits and arranges pages into odd and even numbered pages.\n" - + "ODD_EVEN_MERGE: Merges pages and organises them alternately into odd and even pages.\n" + "REMOVE_FIRST: Removes the first page.\n" + "REMOVE_LAST: Removes the last page.\n" + "REMOVE_FIRST_AND_LAST: Removes both the first and the last pages.\n") diff --git a/app/core/src/main/java/stirling/software/SPDF/service/PdfImageRemovalService.java b/app/core/src/main/java/stirling/software/SPDF/service/PdfImageRemovalService.java deleted file mode 100644 index 029d0924c3..0000000000 --- a/app/core/src/main/java/stirling/software/SPDF/service/PdfImageRemovalService.java +++ /dev/null @@ -1,51 +0,0 @@ -package stirling.software.SPDF.service; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.apache.pdfbox.cos.COSName; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDResources; -import org.apache.pdfbox.pdmodel.graphics.PDXObject; -import org.springframework.stereotype.Service; - -/** Service class responsible for removing image objects from a PDF document. */ -@Service -public class PdfImageRemovalService { - - /** - * Removes all image objects from the provided PDF document. - * - *

This method iterates over each page in the document and removes any image XObjects found - * in the page's resources. - * - * @param document The PDF document from which images will be removed. - * @return The modified PDF document with images removed. - * @throws IOException If an error occurs while processing the PDF document. - */ - public PDDocument removeImagesFromPdf(PDDocument document) throws IOException { - // Iterate over each page in the PDF document - for (PDPage page : document.getPages()) { - PDResources resources = page.getResources(); - // Collect the XObject names to remove - List namesToRemove = new ArrayList<>(); - - // Iterate over all XObject names in the page's resources - for (COSName name : resources.getXObjectNames()) { - // Check if the XObject is an image - if (resources.isImageXObject(name)) { - // Collect the name for removal - namesToRemove.add(name); - } - } - - // Now, modify the resources by removing the collected names - for (COSName name : namesToRemove) { - resources.put(name, (PDXObject) null); - } - } - return document; - } -} diff --git a/app/core/src/main/resources/static/css/home.css b/app/core/src/main/resources/static/css/home.css deleted file mode 100644 index fc1a8c3fb1..0000000000 --- a/app/core/src/main/resources/static/css/home.css +++ /dev/null @@ -1,111 +0,0 @@ -#searchBar { - color: var(--md-sys-color-on-surface); - background-color: var(--md-sys-color-surface-container-low); - width: 100%; - font-size: 16px; - margin-bottom: 2rem; - padding: 0.75rem 3.5rem; - border: 1px solid var(--md-sys-color-outline-variant); - border-radius: 3rem; - outline-color: var(--md-sys-color-outline-variant); -} - -#filtersContainer { - display: flex; - width: 100%; - align-items: center; - justify-content: center; - gap: 10px; -} - -.filter-button { - color: var(--md-sys-color-secondary); - user-select: none; - cursor: pointer; - transition: transform 0.3s; - transform-origin: center center; -} - -.filter-button:hover { - transform: scale(1.08); -} - -.search-icon { - position: absolute; - margin: 0.75rem 1rem; - border: 0.1rem solid transparent; -} - -.favorite-icon { - display: none; - position: absolute; - top: 10px; - right: 10px; - color: var(--md-sys-color-secondary); -} - -#tool-icon { - height: 100%; -} - -#tool-text { - margin: 0.0rem 0 0 1.25rem; -} - -.favorite-icon img { - filter: brightness(0) invert(var(--md-theme-filter-color)); -} - -.favorite-icon:hover .material-symbols-rounded { - transform: scale(1.2); -} - -.favorite-icon .material-symbols-rounded.fill{ - color: #f5c000; -} - -.jumbotron { - padding: 3rem 3rem; - /* Reduce vertical padding */ -} - -.lookatme { - opacity: 1; - position: relative; - display: inline-block; -} - -.lookatme::after { - color: #e33100; - text-shadow: 0 0 5px #e33100; - /* in the html, the data-lookatme-text attribute must */ - /* contain the same text as the .lookatme element */ - content: attr(data-lookatme-text); - padding: inherit; - position: absolute; - inset: 0 0 0 0; - z-index: 1; - /* 20 steps / 2 seconds = 10fps */ - -webkit-animation: 2s infinite Pulse steps(20); - animation: 2s infinite Pulse steps(20); -} - -.newfeature{ - display: flex; - width:fit-content -} -.recent-features{ - display: flex; - flex-direction: row; - max-width: 100%; - overflow: hidden; - justify-content: center; - -} - - .close-icon { - color: var(--favourite-remove) !important; -} - .add-icon { - color: var(--favourite-add) !important; -} diff --git a/app/core/src/main/resources/static/css/multi-tool.css b/app/core/src/main/resources/static/css/multi-tool.css deleted file mode 100644 index 0f104be142..0000000000 --- a/app/core/src/main/resources/static/css/multi-tool.css +++ /dev/null @@ -1,625 +0,0 @@ -/* Base Container Styles */ -.multi-tool-container { - max-width: 100vw; - margin: 0; - padding: 0 20px; -} - -/* Form Elements */ -label { - text-align: left; - display: block; - padding: 0rem 0.25rem; - font-size: 1.25rem; -} - -.form-control { - border-radius: 16px !important; - padding: 0.75rem; - border: 1px solid var(--theme-color-outline-variant); - flex-grow: 5; -} - -/* Action Bar Styles */ -.mt-action-bar { - display: flex; - gap: 10px; - align-items: start; - border: none; - backdrop-filter: blur(2px); - top: 10px; - z-index: 11; - padding: 1.25rem; - border-radius: 2rem; - margin: 0px 25px; - justify-content: center; -} - -.mt-action-bar > * { - padding-bottom: 0.5rem; -} - -.mt-file-uploader { - width: 100%; -} - -.mt-action-bar svg, -.mt-action-btn svg { - width: 20px; - height: 20px; -} - -.mt-action-bar .mt-filename { - width: 100%; - display: flex; - gap: 10px; -} - -/* Action Button Styles */ -.mt-action-btn { - position: fixed; - bottom: 80px; - left: 50%; - transform: translateX(-50%); - border-radius: 2rem; - z-index: 12; - background-color: var(--md-sys-color-surface-container-low); - display: flex; - gap: 10px; - width: fit-content; - justify-content: center; - padding: 10px 20px; - transition: all 0.3s ease-in-out; -} - -.mt-action-btn .btn { - width: 3.5rem; - height: 3.5rem; - border-radius: 20px; - padding: 0; - position: relative; -} - -/* Card and Container Styles */ -.bg-card { - background-color: var(--md-sys-color-surface-5); - border-radius: 3rem; - padding: 15px 0; - margin-left: 55px; - margin-right: 20px; - display: flex; - flex-direction: column; - align-items: stretch; -} - -#pages-container-wrapper { - width: 100%; - display: flex; - justify-content: center; - padding: 0.75rem; - border-radius: 25px; - min-height: 275px; - margin: 0 0 20px 0; -} - -#pages-container { - display: inline-flex; - flex-wrap: wrap; - gap: 20px; - justify-content: flex-start; - width: fit-content; - max-width: 100%; -} - -/* Scrollbar Styles */ -#pages-container-wrapper::-webkit-scrollbar { - width: 10px; - height: 10px; -} - -#pages-container-wrapper::-webkit-scrollbar-track { - background: var(--scroll-bar-color); -} - -#pages-container-wrapper::-webkit-scrollbar-thumb { - border-radius: 10px; - background: var(--scroll-bar-thumb); -} - -#pages-container-wrapper::-webkit-scrollbar-thumb:hover { - background: var(--scroll-bar-thumb-hover); -} - -/* Page Container Base Styles */ -.page-container { - display: inline-block; - list-style-type: none; - width: 260px; - height: 260px; - line-height: 50px; - margin-top: 15px; - box-sizing: border-box; - text-align: center; - aspect-ratio: 1; - position: relative; - user-select: none; - transition: width 0.3s ease-in-out; -} - -/* Responsive Page Container Sizes */ -@media only screen and (max-width: 480px) { - .page-container { - width: calc(100vw - 90px); - height: calc(100vw - 90px); - max-width: 300px; - max-height: 300px; - margin: 5px auto; - } - #pages-container { gap: 10px; } -} - -@media only screen and (min-width: 481px) and (max-width: 768px) { - .page-container { - width: calc((100vw - 120px - 12px) / 2); - height: calc((100vw - 120px - 12px) / 2); - max-width: 250px; - max-height: 250px; - margin: 6px; - } - #pages-container { gap: 12px; } -} - -@media only screen and (min-width: 769px) and (max-width: 1199px) { - .page-container { - width: calc((100vw - 140px - 45px) / 3); - height: calc((100vw - 140px - 45px) / 3); - max-width: 220px; - max-height: 220px; - margin: 7px; - } - #pages-container { gap: 15px; } -} - -@media only screen and (min-width: 1200px) and (max-width: 1280px) { - .page-container { - width: calc((100vw - 160px - 60px) / 4); - height: calc((100vw - 160px - 60px) / 4); - max-width: 200px; - max-height: 200px; - margin: 8px; - } - #pages-container { gap: 17px; } -} - -@media only screen and (min-width: 1281px) { - .page-container { - width: calc((100vw - 180px - 80px) / 5); - height: calc((100vw - 180px - 80px) / 5); - max-width: 190px; - max-height: 190px; - margin: 10px; - } - #pages-container { gap: 20px; } -} - -/* Split Page Styles */ -.page-container.split-before { - border-left: 1px dashed var(--md-sys-color-on-surface); - padding-left: -1px; -} - -.page-container.split-before:first-child { - border-left: none; -} - -.page-container:first-child .pdf-actions_split-file-button { - display: none; -} - -/* RTL Language Support */ -.page-container:last-child:lang(ar), -.page-container:last-child:lang(he), -.page-container:last-child:lang(fa), -.page-container:last-child:lang(ur), -.page-container:last-child:lang(ckb), -.page-container:last-child:lang(ks), -.page-container:last-child:lang(kk), -.page-container:last-child:lang(uz), -.page-container:last-child:lang(ky), -.page-container:last-child:lang(bal), -.page-container:last-child:lang(dv), -.page-container:last-child:lang(ps), -.page-container:last-child:lang(sdg), -.page-container:last-child:lang(syr), -.page-container:last-child:lang(mzn), -.page-container:last-child:lang(tgl), -.page-container:last-child:lang(pnb), -.page-container:last-child:lang(ug), -.page-container:last-child:lang(nqo), -.page-container:last-child:lang(bqi) { - margin-left: auto !important; - margin-right: 0 !important; -} - -/* Page Image Styles */ -.page-container img { - max-width: calc(100% - 8px); - max-height: calc(100% - 8px); - display: block; - position: absolute; - left: 50%; - top: 50%; - translate: -50% -50%; - box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.384); - border-radius: 4px; - transition: rotate 0.3s; -} - -/* Page Number Styles */ -.page-number { - position: absolute; - top: 5px; - left: 5px; - line-height: normal; - color: var(--md-sys-color-on-secondary); - background-color: rgba(162, 201, 255, 0.8); - padding: 6px 8px; - border-radius: 8px; - font-size: 16px; - z-index: 2; - font-weight: 450; -} - -/* Tool Header and Button Styles */ -.tool-header { - margin: 0.5rem 1rem 1rem; -} - -#select-pages-button { - opacity: 0.5; -} - -#add-pdf-button { - margin: 0 auto; -} - -/* Selected Pages Container Styles */ -.selected-pages-container { - background-color: var(--md-sys-color-surface); - border-radius: 16px; - padding: 15px; - width: 100%; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - font-family: Arial, sans-serif; -} - -.selected-pages-container h3 { - color: var(--md-sys-color-on-surface); - font-size: 1.2em; - margin-bottom: 10px; -} - -.selected-pages-header { - margin-bottom: 15px; -} - -.selected-pages-header h5 { - margin: 0 0 8px 0 !important; - font-size: 1.1rem; - font-weight: 600; - color: var(--md-sys-color-on-surface); -} - -.selected-pages-header #csv-input { - width: 100%; - height: 2.5rem; - border-radius: 8px; - border: 1px solid var(--md-sys-color-outline); - background-color: var(--md-sys-color-surface); - color: var(--md-sys-color-on-surface); - padding: 0 12px; - font-size: 0.95rem; -} - -.selected-pages-header #csv-input:focus { - outline: none; - border-color: var(--md-sys-color-primary); - box-shadow: 0 0 0 2px rgba(var(--md-sys-color-primary-rgb), 0.2); -} - -/* Pages List Styles */ -.pages-list { - display: flex; - flex-wrap: wrap; - gap: 10px; - padding: 0; - list-style: none; - min-height: 0; - max-height: 10rem; - overflow: auto; -} - -.page-item { - background-color: var(--md-sys-color-surface-container-low); - border-radius: 8px; - padding: 8px 12px; - display: flex; - align-items: center; - gap: 8px; - font-weight: bold; - color: var(--md-sys-color-on-surface); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - width: 7rem; - height: 2.5rem; -} - -.selected-page-number { - width: 4rem; - font-size: small; -} - -.remove-btn { - cursor: pointer; - color: var(--md-sys-color-on-surface); - font-size: 1.2em; -} - -.checkbox-container { - align-items: center; - justify-content: center; - display: flex; - flex-direction: column; -} - -.checkbox-label { - font-size: medium; -} - -/* Zoom Level Responsive Styles */ -@media only screen and (min-width: 2000px) { - .mt-action-btn { - gap: 12px; - padding: 12px 24px; - border-radius: 2.5rem; - } - .mt-action-btn .btn { - width: 4rem; - height: 4rem; - border-radius: 22px; - } - .mt-action-btn .btn .material-symbols-rounded { - font-size: 1.7rem; - } -} - -@media only screen and (min-width: 2560px) { - .mt-action-btn { - gap: 15px; - padding: 15px 30px; - border-radius: 3rem; - } - .mt-action-btn .btn { - width: 5rem; - height: 5rem; - border-radius: 28px; - } - .mt-action-btn .btn .material-symbols-rounded { - font-size: 2.1rem; - } -} - -@media only screen and (min-width: 3840px) { - .mt-action-btn { - gap: 20px; - padding: 20px 40px; - border-radius: 4rem; - } - .mt-action-btn .btn { - width: 7rem; - height: 7rem; - border-radius: 40px; - } - .mt-action-btn .btn .material-symbols-rounded { - font-size: 3rem; - } -} - -@media only screen and (min-width: 7680px) { - .mt-action-btn { - gap: 40px; - padding: 40px 80px; - border-radius: 8rem; - } - .mt-action-btn .btn { - width: 14rem; - height: 14rem; - border-radius: 80px; - } - .mt-action-btn .btn .material-symbols-rounded { - font-size: 6rem; - } -} - -/* Zoom-responsive Sidebar Styles */ -@media only screen and (max-width: 1280px) { - .container { - position: relative; - } - - .mt-action-btn { - position: fixed !important; - left: 10px !important; - top: 80px !important; - bottom: 20px !important; - margin: 0 !important; - transform: none !important; - border-radius: 16px !important; - background-color: var(--md-sys-color-surface-container-low); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - backdrop-filter: blur(8px); - display: flex !important; - flex-direction: column !important; - align-items: center !important; - justify-content: center !important; - height: calc(100vh - 100px) !important; - overflow-y: auto !important; - scrollbar-width: none !important; - -ms-overflow-style: none !important; - z-index: 12; - width: 70px !important; - gap: 8px !important; - padding: 12px 8px !important; - box-sizing: border-box !important; - } - - .mt-action-btn:has(.btn:nth-last-child(n+7)) { - justify-content: flex-start !important; - } - - .mt-action-btn::-webkit-scrollbar { - display: none !important; - } - - .mt-action-btn .btn { - width: 48px !important; - height: 48px !important; - border-radius: 12px !important; - padding: 0 !important; - margin: 0 !important; - flex-shrink: 0; - font-size: 16px !important; - box-sizing: border-box !important; - } - - .mt-action-btn .btn .material-symbols-rounded { - font-size: 20px !important; - line-height: 1 !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - width: 100% !important; - height: 100% !important; - font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24 !important; - } - - .multi-tool-container { - margin-left: 0 !important; - max-width: 100% !important; - } - - .mt-action-bar { - margin-left: 25px !important; - margin-right: 25px !important; - } - - .mt-action-btn:not(:has(*:nth-child(6))) { - justify-content: center !important; - } -} - -/* Mobile and High Zoom Responsive Styles */ -@media only screen and (max-width: 960px) { - .mt-action-btn { - left: 8px !important; - top: 75px !important; - bottom: 15px !important; - height: calc(100vh - 90px) !important; - } -} - -@media only screen and (max-width: 768px) { - .mt-action-btn { - left: 5px !important; - width: 60px !important; - top: calc(var(--navbar-height, 60px) + 10px) !important; - bottom: 10px !important; - height: calc(100vh - var(--navbar-height, 60px) - 20px) !important; - } - - .mt-action-btn .btn { - width: 40px !important; - height: 40px !important; - box-sizing: border-box !important; - } - - .mt-action-btn .btn .material-symbols-rounded { - font-size: 16px !important; - } - - #pages-container { - margin: 0 auto !important; - display: flex !important; - flex-wrap: wrap !important; - justify-content: center !important; - width: 100% !important; - } - - .page-container { - width: calc(100vw - 55px) !important; - height: calc(100vw - 60px) !important; - max-width: 350px !important; - max-height: 350px !important; - margin: 5px auto !important; - } -} - -@media only screen and (max-width: 480px) { - .mt-action-btn { - left: 2px !important; - top: calc(var(--navbar-height, 60px) + 5px) !important; - bottom: 5px !important; - height: calc(100vh - var(--navbar-height, 60px) - 10px) !important; - width: 50px !important; - gap: 6px !important; - padding: 10px 6px !important; - } - - .mt-action-btn .btn { - width: 32px !important; - height: 32px !important; - border-radius: 10px !important; - box-sizing: border-box !important; - } - - .mt-action-btn .btn .material-symbols-rounded { - font-size: 14px !important; - } -} - -@media only screen and (max-width: 384px) { - .mt-action-btn { - left: 2px !important; - top: calc(var(--navbar-height, 60px) + 5px) !important; - bottom: 5px !important; - height: calc(100vh - var(--navbar-height, 60px) - 10px) !important; - width: 40px !important; - gap: 4px !important; - padding: 8px 4px !important; - } - - .mt-action-btn .btn { - width: 28px !important; - height: 28px !important; - border-radius: 8px !important; - box-sizing: border-box !important; - } - - .mt-action-btn .btn .material-symbols-rounded { - font-size: 12px !important; - } - - .page-container { - width: calc(100vw - 55px) !important; - height: calc(100vw - 60px) !important; - max-width: 280px !important; - max-height: 280px !important; - margin: 3px auto !important; - } - - .page-container img { - max-width: calc(100% - 4px) !important; - max-height: calc(100% - 4px) !important; - } -} - - diff --git a/app/core/src/main/resources/static/css/navbar.css b/app/core/src/main/resources/static/css/navbar.css deleted file mode 100644 index 20cd191765..0000000000 --- a/app/core/src/main/resources/static/css/navbar.css +++ /dev/null @@ -1,799 +0,0 @@ -#navbarSearch { - top: 100%; - right: 0; - transition: all 0.3s; - max-height: 0; - overflow: hidden; -} - -#navbarSearch.show { - height: auto; - /*dynamically changes height*/ -} - -#searchResults .dropdown-item { - display: flex; - align-items: center; - white-space: nowrap; - height: 50px; - /* Fixed height */ - overflow: hidden; - /* Hide overflow */ -} - -#searchResults .icon { - margin-right: 10px; -} - -#searchResults .icon-text { - display: inline; - overflow: hidden; - /* Hide overflow */ - text-overflow: ellipsis; - /* Add ellipsis for long text */ -} - -#search-icon i { - font-size: 24px; - /* Adjust this to your desired size */ - transition: color 0.3s; -} - -#search-icon:hover i { - color: #666; - /* Adjust this to your hover color */ -} - -.search-input { - transition: border 0.3s, box-shadow 0.3s; -} - -.search-input:focus { - border-color: #666; - /* Adjust this to your focus color */ - box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); - /* Adjust this to your desired shadow */ -} - - -.main-icon { - width: 36px; - height: 36px; - vertical-align: middle; - transform: translateY(-2px); -} - -/* Responsive navbar brand - prevent hamburger from wrapping */ -.navbar-brand { - display: flex !important; - align-items: center; - gap: 8px; - min-width: 0; /* Allow shrinking */ -} - -.navbar-brand .icon-text { - font-size: 1.5rem; - font-weight: bold; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -/* Progressive text shrinking to prevent hamburger wrap */ -@media only screen and (max-width: 480px) { - .navbar-brand .icon-text { - font-size: 1.3rem; - } - - .main-icon { - width: 32px; - height: 32px; - } -} - -@media only screen and (max-width: 420px) { - .navbar-brand .icon-text { - font-size: 1.1rem; - } - - .main-icon { - width: 28px; - height: 28px; - } -} - -@media only screen and (max-width: 380px) { - .navbar-brand .icon-text { - font-size: 1rem; - } - - .main-icon { - width: 24px; - height: 24px; - } -} - -@media only screen and (max-width: 340px) { - .navbar-brand .icon-text { - font-size: 0.9rem; - } - - .main-icon { - width: 20px; - height: 20px; - } -} - -/* Ultra-small screens - hide text, keep icon */ -@media only screen and (max-width: 320px) { - .navbar-brand .icon-text { - display: none; - } - - .main-icon { - width: 24px; - height: 24px; - } -} - -.nav-icon { - vertical-align: middle; - font-size: 2rem !important; - padding: 0.25rem; - border-radius: 1rem; -} - -.icon+.icon { - margin-left: -4px; -} - -.icon-text { - margin-left: 8px; - margin-right: 4px; - display: inline-flex; - flex-direction: column; - justify-content: space-between; - vertical-align: middle; -} - - -.scalable-languages-container { - display: grid; - grid-template-columns: repeat(1, 1fr); -} - -@media (min-width: 400px) { - #languageSelection.scalable-languages-container { - grid-template-columns: repeat(2, 1fr); - } -} -@media (min-width: 600px) { - #languageSelection.scalable-languages-container { - grid-template-columns: repeat(3, 1fr); - } -} -@media (min-width: 900px) { - #languageSelection.scalable-languages-container { - grid-template-columns: repeat(4, 1fr) !important; - } -} - -.scalable-languages-container:not(:has(> :nth-child(4))) .lang-dropdown-item-wrapper:last-child { - border: 0px !important -} - -html[dir="ltr"] #languageSelection .lang-dropdown-item-wrapper:last-child { - border-right: none !important; -} - -#languageSelection .lang-dropdown-item-wrapper:last-child { - border: 0px !important; -} -@media (max-width: 600px) { - .scalable-languages-container { - grid-template-columns: repeat(2, 1fr); - } - - .scalable-languages-container:not(:has(> :nth-child(2))) { - grid-template-columns: repeat(var(--count), 1fr) !important; - } - - .scalable-languages-container .lang-dropdown-item-wrapper:nth-child(2n) { - border: 0px - } -} - -@media (min-width: 601px) and (max-width: 900px) { - .scalable-languages-container { - grid-template-columns: repeat(3, 1fr); - } - - .scalable-languages-container:not(:has(> :nth-child(3))) { - grid-template-columns: repeat(var(--count), 1fr) !important; - } - - .scalable-languages-container .lang-dropdown-item-wrapper:nth-child(3n) { - border: 0px - } -} - -@media (min-width: 901px) { - .scalable-languages-container { - grid-template-columns: repeat(4, 1fr); - } - - .scalable-languages-container:not(:has(> :nth-child(4))) { - grid-template-columns: repeat(var(--count), 1fr) !important; - } - - .scalable-languages-container .lang-dropdown-item-wrapper:nth-child(4n) { - border: 0px - } -} - -.scalable-languages-container:has(> *:nth-child(1)) { - --count: 1; -} - -.scalable-languages-container:has(> *:nth-child(2)) { - --count: 2; -} - -.scalable-languages-container:has(> *:nth-child(3)) { - --count: 3; -} - -html[dir="ltr"] .lang-dropdown-item-wrapper { - border-right: 2px solid var(--md-nav-color-on-separator); -} - -html[dir="rtl"] .lang-dropdown-item-wrapper { - border-left: 2px solid var(--md-nav-color-on-separator); -} - -.scroll-lock-y { - overflow-y: auto; - max-height: 30vh; - overscroll-behavior-y: contain; - -webkit-overflow-scrolling: touch; -} - -/* Responsive adjustments */ -@media (min-width: 1200px) { - .lang-dropdown-item-wrapper .dropdown-item { - min-width: 200px -} - -.scroll-lock-y { - max-height: 80vh; -} -} - -.dropdown-item .icon-text { - text-wrap: wrap; - word-break: break-word; - width: 80%; - font-size: large; -} - -.close-icon { - color: var(--md-sys-color-secondary); -} - -.close-icon:hover { - transform: scale(1.15); -} - -span.icon-text::after { - content: attr(data-text); - content: attr(data-text) / ""; - font-weight: 600; - height: 0; - visibility: hidden; - overflow: hidden; - user-select: none; - pointer-events: none; -} - -.nav-item-separator { - position: relative; - margin: 0 4px; - /* Adjust the margin as needed */ -} - -.nav-item-separator::before { - content: ""; - position: absolute; - left: 0; - top: 10%; - /* Adjust the top and bottom margins as needed */ - bottom: 10%; - width: 1px; - background-color: #ccc; - /* Adjust the color as needed */ -} - -.navbar-icon { - width: 20px; - height: 20px; - transform: translateY(-2px); -} - -.navbar-toggler { - color: var(--md-sys-color-on-surface-variant); -} - -.nav-link { - display: flex; - align-items: center; - max-width: 98vw; -} - -.chevron-icon { - margin-left: auto; - transition: transform 0.3s ease; -} - -[aria-expanded="true"] > .chevron-icon { - transform: rotate(180deg); -} - -.nav-item { - position: relative; -} - -/* .tooltip-text { - visibility: hidden; - background-color: rgb(71 67 67 / 80%); - color: #fff; - text-align: center; - border-radius: 4px; - padding: 5px; - position: absolute; - z-index: 1; - top: 100%; - left: 50%; - transform: translateX(-50%); - opacity: 0; - transition: opacity 0.3s; - font-size: 12px; - white-space: nowrap; - margin-top: 5px; -} - -.nav-item:hover .tooltip-text { - visibility: visible; - opacity: 1; -} */ - -.dropdown-menu.scrollable-y { - overflow-y: scroll; - max-height: 360px; -} - -/* Dropdown Scrollbar*/ -.scrollable-y { - overflow-y: scroll; - max-height: 190px; - overscroll-behavior: contain; -} - -.scrollable-y::-webkit-scrollbar { - background: transparent; - width: 0.7rem; -} - -.scrollable-y::-webkit-scrollbar-track { - background: transparent; -} - -.scrollable-y::-webkit-scrollbar-thumb { - border-radius: 2rem; - background-color: var(--md-sys-color-surface-5); - border: 3px solid var(--md-sys-color-surface-5); -} - -/* Mega Menu */ -.dropdown-mega .dropdown-menu { - width: 100%; - left: 0 !important; - right: auto !important; -} - -.dropdown-mega .title { - padding-bottom: 1rem; - margin: 0; -} - -.dropdown-menu .list-group { - padding: 1rem; -} - -.mega-content .dropdown-item:focus .nav-icon, -.mega-content .dropdown-item:hover .nav-icon, -.mega-content .dropdown-item.active .nav-icon { - background-color: transparent; -} - -.dropdown-item:focus.sign, -.dropdown-item:hover.sign, -.dropdown-item.active.sign { - color: var(--md-nav-on-section-color-sign); - background-color: var(--md-nav-section-color-sign); -} - -.dropdown-item:focus.organize, -.dropdown-item:hover.organize, -.dropdown-item.active.organize { - color: var(--md-nav-on-section-color-organize); - background-color: var(--md-nav-section-color-organize); -} - -.dropdown-item:focus.convert, -.dropdown-item:hover.convert, -.dropdown-item.active.convert { - color: var(--md-nav-on-section-color-convert); - background-color: var(--md-nav-section-color-convert); -} - -.dropdown-item:focus.convertto, -.dropdown-item:hover.convertto, -.dropdown-item.active.convertto { - color: var(--md-nav-on-section-color-convertto); - background-color: var(--md-nav-section-color-convertto); -} - -.dropdown-item:focus.image, -.dropdown-item:hover.image, -.dropdown-item.active.image { - color: var(--md-nav-on-section-color-image); - background-color: var(--md-nav-section-color-image); -} - -.dropdown-item:focus.word, -.dropdown-item:hover.word, -.dropdown-item.active.word { - color: var(--md-nav-on-section-color-word); - background-color: var(--md-nav-section-color-word); -} - -.dropdown-item:focus.ppt, -.dropdown-item:hover.ppt, -.dropdown-item.active.ppt { - color: var(--md-nav-on-section-color-ppt); - background-color: var(--md-nav-section-color-ppt); -} - -.dropdown-item:focus.security, -.dropdown-item:hover.security, -.dropdown-item.active.security { - color: var(--md-nav-on-section-color-security); - background-color: var(--md-nav-section-color-security); -} - -.dropdown-item:focus.other, -.dropdown-item:hover.other, -.dropdown-item.active.other { - color: var(--md-nav-on-section-color-other); - background-color: var(--md-nav-section-color-other); -} - -.dropdown-item:focus.advance, -.dropdown-item:hover.advance, -.dropdown-item.active.advance { - color: var(--md-nav-on-section-color-advance); - background-color: var(--md-nav-section-color-advance); -} - -/* Dropdown min-width */ -.dropdown-mw-28 { - min-width: 280px; - min-height: 50px; -} - -.dropdown-mw-20 { - min-width: 200px; -} - -/* Dropdown open on hover */ -html[dir="ltr"] .dropdown-menu { - padding-top: 0.5rem; - top: auto; - left: auto; - right: 0; -} - -html[dir="rtl"] .dropdown-menu { - padding-top: 0.5rem; - top: auto; - left: 0; - right: auto; -} - -/* Bootstrap Popper positioning overrides removed - dropdowns now position naturally relative to their buttons */ - -.dropdown-menu-wrapper { - padding: 1.5rem 0; - border-radius: 1rem; - color: var(--md-sys-color-on-surface); - background-color: var(--md-sys-color-surface); - border: 1px solid var(--md-sys-color-surface-5); - box-shadow: var(--md-sys-elevation-2); -} - - -.dropdown-menu-tp { - color: transparent; - background-color: transparent; - border: none; - box-shadow: none; -} - -.icon-hide { - display: inline-flex; -} - -@media (max-width:1199.98px) { - .navbar-collapse .dropdown-menu { - width: 100%; - } - .navbar-collapse .dropdown-menu-wrapper { - width: 100%; - box-sizing: border-box; - } - .navbar-collapse .dropdown-mw-28 { - min-width: 0; - } -} - -@media (min-width:1200px) { - /* This CSS-based hover is disabled because it conflicts with Bootstrap's JavaScript. - Hover functionality is now handled in navbar.js and search.js */ - /* - .dropdown:hover .dropdown-menu { - display: block; - margin-top: 0; - } - */ - - .icon-hide { - display: none; - } - .chevron-icon { - display: none !important; - } -} - -@media (max-width: 1199.98px) { - .navbar-collapse .dropdown-menu { - width: 100vw !important; - max-width: 100vw !important; - left: 0 !important; - right: 0 !important; - transform: none !important; - transform-origin: none !important; - } - - .navbar .navbar-expand-xl .dropdown-menu, - .navbar-expand .dropdown-menu { - transform: none !important; - transform-origin: none !important; - left: 0 !important; - right: 0 !important; - } - - .navbar-collapse .dropdown-mega .dropdown-menu { - max-height: 60vh; - overflow-y: auto; - } - .navbar-collapse .dropdown-mega .dropdown-menu-wrapper { - border-radius: 0; - } - - #favoritesDropdown, - #languageDropdown + .dropdown-menu .dropdown-menu-wrapper, - #searchDropdown + .dropdown-menu .dropdown-menu-wrapper { - padding: 1.5rem 1rem; - } - - #favoritesDropdown, #languageSelection, #searchResults { - width: 100%; - box-sizing: border-box; - } - - .navbar-collapse .dropdown-menu-wrapper { - width: 95vw; - box-sizing: border-box; - margin-left: 0 !important; - padding: 0 !important; - } - .navbar-collapse .dropdown-mw-28 { - min-width: 0; - } - .icon-hide { - display: inline-flex; - } - - .navbar-collapse .dropdown-item { - margin-left: 0 !important; - } - - .container { - margin-left: auto !important; - margin-right: auto !important; - padding-left: 4px !important; - } - - #mainNavbarDropdownMenu { - transform: none !important; - transform-origin: none !important; - left: 0 !important; - right: 0 !important; - width: 100vw !important; - margin-bottom: 0 !important; - } - - .dropdown-menu, - .dropdown-menu-tp, - .dropdown-menu-tp.show { - transform: none !important; - transform-origin: none !important; - max-width: 95vw !important; - width: 100vw !important; - left: 0 !important; - right: 0 !important; - margin-bottom: 0 !important; - } - - -} - -.go-pro-link { - position: relative; - padding: 0.5rem 1rem; - transition: all 0.3s ease; - z-index: 1; - display: inline-block; - width: auto; -} - -.go-pro-badge { - display: inline-block; - padding: 0.25rem 0.5rem; - font-size: 0.75rem; - font-weight: bold; - color: #ffffff; - background-color: #007bff; - border-radius: 0.25rem; - text-transform: uppercase; - transition: all 0.3s ease; -} - -.go-pro-link:hover .go-pro-badge { - background-color: #0056b3; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); -} - -#stacked { - gap: 1rem; -} - -#stacked>.navbar-item { - margin: 0; -} - -.features-container { - display: flex; - gap: 30px; - justify-content: center; - width: 100%; - position: relative; -} - -.feature-group { - display: flex; - flex-direction: column; - min-width: 14rem; - max-width: 18rem; - flex: 1 1 min(14rem, 100%); - box-sizing: border-box; -} - -@media (max-width: 768px) { - .feature-group { - min-width: 10rem; - } -} - -.feature-rows { - display: flex; - flex-wrap: wrap; - justify-content: flex-start; - gap: 1rem; - padding: 0 1rem; - box-sizing: border-box; -} - -.feature-rows.single-column { - justify-content: center; - /* Center-align a single column */ -} - -.feature-group-header { - display: flex; - justify-content: flex-start; - color: var(--md-sys-color-on-surface); - margin-top: 15px; - user-select: none; - cursor: pointer; - gap: 10px; -} - -.nav-group-container { - padding: 10px; -} - - - -.card-title.text-primary { - color: #000; -} - -.home-card-icon { - width: 3rem; - height: 3rem; - transform: translateY(-5px); -} - -.favorite-icon { - display: none; - position: absolute; - top: 10px; - right: 10px; - color: var(--md-sys-color-secondary); -} - -.favorite-icon img { - filter: brightness(0) invert(var(--md-theme-filter-color)); -} - -.favorite-icon:hover .material-symbols-rounded { - transform: scale(1.2); -} - -.favorite-icon .material-symbols-rounded.fill { - color: #f5c000; -} - - -@keyframes Pulse { - from { - opacity: 0; - } - - 50% { - opacity: 1; - } - - to { - opacity: 0; - } -} - -.update-notice { - animation: scale 1s infinite alternate; -} - -@keyframes scale { - 0% { - transform: scale(0.96); - } - - 100% { - transform: scale(1); - } -} - -.hidden { - visibility: hidden; -} diff --git a/app/core/src/main/resources/static/css/pdfActions.css b/app/core/src/main/resources/static/css/pdfActions.css deleted file mode 100644 index bde4e5bac4..0000000000 --- a/app/core/src/main/resources/static/css/pdfActions.css +++ /dev/null @@ -1,117 +0,0 @@ -.pdf-actions_button-container { - z-index: 4; - opacity: 0; - transition: opacity 0.1s linear; - - position: absolute !important; - bottom: 0px; - left: 50%; - transform: translate(-50%, 0%); -} - -.pdf-actions_container:hover .pdf-actions_button-container { - opacity: 1; -} - -.pdf-actions_button-container>* { - padding: 0.25rem 0.5rem; - display: block; -} - -.pdf-actions_button-container>*:focus { - box-shadow: none !important; -} - -.pdf-actions_button-container .btn { - border-radius: 12px; -} - -.pdf-actions_button-container> :first-child, -.pdf-actions_container:first-child>.pdf-actions_button-container> :first-child+* { - border-radius: 12px 0px 0px 12px; -} - -.pdf-actions_container svg { - width: 16px; - height: 16px; -} - -.pdf-actions_container:nth-child(1) .pdf-actions_move-left-button { - display: none; -} - -.pdf-actions_container:last-child .pdf-actions_move-right-button { - display: none; -} - -/* "insert pdf" buttons that appear on the right when hover */ -.pdf-actions_insert-file-button-container { - display:flex; - flex-direction: column; - gap: 10px; - translate: 0 -50%; - height: 100%; - width: 100px; - padding-left: 30px; - padding-right:30px; - justify-content: center; - z-index: 3; - opacity: 0; - transition: opacity 0.4s; - -} - -.pdf-actions_insert-file-button-container.left { - left: -50px; -} - -.pdf-actions_insert-file-button-container.right { - right: -50px; -} - -html[dir="ltr"] .pdf-actions_insert-file-button-container.right { - display: none; -} - -html[dir="rtl"] .pdf-actions_insert-file-button-container.left { - display: none; -} - -html[dir="ltr"] .pdf-actions_container:last-child>.pdf-actions_insert-file-button-container.right { - display: flex; -} - -html[dir="rtl"] .pdf-actions_container:last-child>.pdf-actions_insert-file-button-container.left { - display: flex; -} - -.pdf-actions_insert-file-button-container.left button, -.pdf-actions_insert-file-button-container.right button { - padding: 0.45rem; -} - -.pdf-actions_button-container button .material-symbols-rounded { - font-size: 1.25rem; - vertical-align: sub; -} - -.pdf-actions_insert-file-button-container:hover { - opacity: 1; - transition: opacity 0.05s; -} - -.pdf-actions_checkbox { - position: absolute; - top: 5px; - right: 3px; - color: var(--md-sys-color-on-surface); - background-color: var(--md-sys-color-surface-5); - padding: 6px 8px; - border-radius: 8px; - font-size: 16px; - z-index: 10; -} - -.hidden { - display: none; -} \ No newline at end of file diff --git a/app/core/src/main/resources/static/css/removeImage.css b/app/core/src/main/resources/static/css/removeImage.css deleted file mode 100644 index 4f2be4034d..0000000000 --- a/app/core/src/main/resources/static/css/removeImage.css +++ /dev/null @@ -1,22 +0,0 @@ -.filename { - flex-grow: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-right: 10px; -} - -.arrows { - flex-shrink: 0; - display: flex; - justify-content: flex-end; -} -.arrows .btn { - margin: 0 3px; -} - -.move-up span, -.move-down span { - font-weight: bold; - font-size: 1.2em; -} diff --git a/app/core/src/main/resources/static/css/theme/componentes.css b/app/core/src/main/resources/static/css/theme/componentes.css deleted file mode 100644 index a113015eb9..0000000000 --- a/app/core/src/main/resources/static/css/theme/componentes.css +++ /dev/null @@ -1,973 +0,0 @@ -/* Dark Mode Styles */ -body, -select, -textarea { - background-color: var(--md-sys-color-surface); - color: var(--md-sys-color-on-surface); -} - -.transition-theme { - transition: background 0.5s ease, color 0.5s ease, border 0.5s ease; -} - - -/*.global-buttons-container input:disabled::-webkit-input-placeholder { !* WebKit browsers *!*/ -/* color: #98A0AB;*/ -/*}*/ -/*.global-buttons-container input:disabled:-moz-placeholder { !* Mozilla Firefox 4 to 18 *!*/ -/* color: #98A0AB;*/ -/*}*/ -/*.global-buttons-container input:disabled::-moz-placeholder { !* Mozilla Firefox 19+ *!*/ -/* color: #98A0AB;*/ -/*}*/ -/*.global-buttons-container input:disabled:-ms-input-placeholder { !* Internet Explorer 10+ *!*/ -/* color: #98A0AB;*/ -/*}*/ -/* Scrollbar */ -*::-webkit-scrollbar { - background: var(--md-sys-color-surface); - width: 1rem; -} - -*::-webkit-scrollbar-track { - background: var(--md-sys-color-surface); -} - -*::-webkit-scrollbar-thumb { - border-radius: 2rem; - background-color: var(--md-sys-color-surface-5); - border: 5px solid var(--md-sys-color-surface-5); -} - -*::-webkit-scrollbar-corner { - background-color: var(--md-sys-color-surface); -} - -/* Alerts */ -.alert { - border-radius: 3rem; -} - -/* Table */ -td { - word-break: break-word; -} - -.input-group-append { - margin: 0rem 0.5rem !important; -} - -.card-header { - background-color: transparent; - border-bottom: none; -} - -.bg-card { - background-color: var(--md-sys-color-surface-5); - border-radius: 3rem; - padding: 2.5rem; - - max-width: 95vw; - margin-left: 2vw; -} - -.card { - padding: 1.25rem; - border-radius: 2rem; - background-color: var(--md-sys-color-surface-5); - border: none; -} - -/* Modal */ -.modal-content { - background-color: var(--md-sys-color-surface); - border-radius: 2rem; - border: transparent; -} - -.modal-header, -.modal-body, -.modal-footer { - background-color: var(--md-sys-color-surface-5); - - border: none; -} - -.modal-header { - border-radius: 2rem 2rem 0rem 0rem; - padding: 1.5rem 2rem 0.5rem; -} - -.modal-body{ - padding: 0.5rem 2rem; -} - -.modal-footer { - border-radius: 0rem 0rem 2rem 2rem; - padding: 0.5rem 2rem 1.5rem; -} - -/* Icon fill */ -.material-symbols-rounded { - vertical-align: text-top; -} - -/* Navbar Icon*/ -.nav-icon { - color: var(--md-sys-color-surface); -} - -.sign .nav-icon, -.sign.tool-header-icon { - color: var(--md-nav-on-section-color-sign); - background-color: var(--md-nav-section-color-sign); -} - -.organize .nav-icon, -.organize.tool-header-icon { - color: var(--md-nav-on-section-color-organize); - background-color: var(--md-nav-section-color-organize); -} - -.convert .nav-icon, -.convert.tool-header-icon { - color: var(--md-nav-on-section-color-convert); - background-color: var(--md-nav-section-color-convert); -} - -.convertto .nav-icon, -.convertto.tool-header-icon { - color: var(--md-nav-on-section-color-convertto); - background-color: var(--md-nav-section-color-convertto); -} - - -.security .nav-icon, -.security.tool-header-icon { - color: var(--md-nav-on-section-color-security); - background-color: var(--md-nav-section-color-security); -} - -.other .nav-icon, -.other.tool-header-icon { - color: var(--md-nav-on-section-color-other); - background-color: var(--md-nav-section-color-other); -} - -.advance .nav-icon, -.advance.tool-header-icon { - color: var(--md-nav-on-section-color-advance); - background-color: var(--md-nav-section-color-advance); -} - -.image .nav-icon, -.image.tool-header-icon { - color: var(--md-nav-on-section-color-image); - background-color: var(--md-nav-section-color-image); -} - -.word .nav-icon, -.word.tool-header-icon { - color: var(--md-nav-on-section-color-word); - background-color: var(--md-nav-section-color-word); -} - -.ppt .nav-icon, -.ppt.tool-header-icon { - color: var(--md-nav-on-section-color-ppt); - background-color: var(--md-nav-section-color-ppt); -} - -/* Tool Page Header*/ -.tool-header { - margin-bottom: 2rem; -} - -.tool-header .tool-header-icon { - margin: 0px 1rem; - height: 4rem; - width: 4rem; - border-radius: 25px; - font-size: 3rem; - padding: 0.5rem; - vertical-align: middle; - pointer-events: none; - user-select: none; - -webkit-user-select: none; - -webkit-tap-highlight-color: rgb(0 0 0 / 0%); -} - -.tool-header .tool-header-text { - font-size: 2.5rem; - font-weight: 400; - vertical-align: middle; -} - -/* Home Card Colors*/ -.feature-card .nav-icon { - vertical-align: middle; - font-size: 2rem !important; - padding: 0.75rem; - border-radius: 0.9rem; - color: var(--md-sys-color-surface); -} - -.feature-card .sign .nav-icon { - color: var(--md-nav-on-section-color-sign); - background-color: var(--md-nav-section-color-sign); -} - -.feature-card .organize .nav-icon { - color: var(--md-nav-on-section-color-organize); - background-color: var(--md-nav-section-color-organize); -} - -.feature-card .convert .nav-icon { - color: var(--md-nav-on-section-color-convert); - background-color: var(--md-nav-section-color-convert); -} - -.feature-card .convertto .nav-icon { - color: var(--md-nav-on-section-color-convertto); - background-color: var(--md-nav-section-color-convertto); -} - -.feature-card .security .nav-icon { - color: var(--md-nav-on-section-color-security); - background-color: var(--md-nav-section-color-security); -} - -.feature-card .other .nav-icon { - color: var(--md-nav-on-section-color-other); - background-color: var(--md-nav-section-color-other); -} - -.feature-card .advance .nav-icon { - color: var(--md-nav-on-section-color-advance); - background-color: var(--md-nav-section-color-advance); -} - -.feature-card .image .nav-icon { - color: var(--md-nav-on-section-color-image); - background-color: var(--md-nav-section-color-image); -} - -.feature-card .word .nav-icon { - color: var(--md-nav-on-section-color-word); - background-color: var(--md-nav-section-color-word); -} - -.feature-card .ppt .nav-icon { - color: var(--md-nav-on-section-color-ppt); - background-color: var(--md-nav-section-color-ppt); -} - -/* Buttons Components */ -.btn { - border-radius: 1.25rem; -} - -.btn-close { - width: auto; - height: auto; - color: var(--md-sys-color-on-surface); - background: transparent; -} - -.btn-close:hover { - color: var(--md-sys-color-on-surface); -} - -.modal-header .btn-close { - margin: 0; -} - -/* Primary btn */ -.btn-primary { - color: var(--md-sys-color-on-primary); - background-color: var(--md-sys-color-primary); - border-color: var(--md-sys-color-primary); - box-shadow: none !important; -} - -.btn-primary.disabled, -.btn-primary:disabled { - color: var(--md-sys-color-on-primary); - background-color: var(--md-sys-color-primary); - border-color: var(--md-sys-color-primary); -} - -.btn-primary:hover { - color: var(--md-sys-color-on-primary); - background-color: var(--md-sys-color-primary); - border-color: var(--md-sys-color-primary); - box-shadow: var(--md-sys-elevation-3) !important; -} - -.btn-check:active+.btn-primary, -.btn-check:checked+.btn-primary, -.btn-primary.active, -.btn-primary:active, -.show>.btn-primary.dropdown-toggle { - color: var(--md-sys-color-on-primary); - background-color: var(--md-sys-color-primary); - border-color: var(--md-sys-color-primary); - box-shadow: var(--md-sys-elevation-3) !important; -} - -.btn-check:focus+.btn-primary, -.btn-primary:focus { - color: var(--md-sys-color-on-primary); - background-color: var(--md-sys-color-primary); - border-color: var(--md-sys-color-primary); - box-shadow: var(--md-sys-elevation-3) !important; -} - -/* Secondary btn */ -.btn-secondary { - color: var(--md-sys-color-on-secondary); - background-color: var(--md-sys-color-secondary); - border-color: var(--md-sys-color-secondary); - box-shadow: none !important; -} - -.btn-secondary.disabled, -.btn-secondary:disabled { - color: var(--md-sys-color-on-secondary); - background-color: var(--md-sys-color-secondary); - border-color: var(--md-sys-color-secondary); -} - -.btn-secondary:hover { - color: var(--md-sys-color-on-secondary); - background-color: var(--md-sys-color-secondary); - border-color: var(--md-sys-color-secondary); - box-shadow: var(--md-sys-elevation-3) !important; -} - -.btn-check:active+.btn-secondary, -.btn-check:checked+.btn-secondary, -.btn-secondary.active, -.btn-secondary:active, -.show>.btn-secondary.dropdown-toggle { - color: var(--md-sys-color-on-secondary); - background-color: var(--md-sys-color-secondary); - border-color: var(--md-sys-color-secondary); - box-shadow: var(--md-sys-elevation-3) !important; -} - -.btn-check:focus+.btn-secondary, -.btn-secondary:focus { - color: var(--md-sys-color-on-secondary); - background-color: var(--md-sys-color-secondary); - border-color: var(--md-sys-color-secondary); - box-shadow: var(--md-sys-elevation-3) !important; -} - -/* Danger btn */ -.btn-danger { - color: var(--md-sys-color-on-error); - background-color: var(--md-sys-color-error); - border-color: var(--md-sys-color-error); - box-shadow: none !important; -} - -.btn-danger.disabled, -.btn-danger:disabled { - color: var(--md-sys-color-on-error); - background-color: var(--md-sys-color-error); - border-color: var(--md-sys-color-error); -} - -.btn-danger:hover { - color: var(--md-sys-color-on-error); - background-color: var(--md-sys-color-error); - border-color: var(--md-sys-color-error); - box-shadow: var(--md-sys-elevation-3) !important; -} - -.btn-check:active+.btn-danger, -.btn-check:checked+.btn-danger, -.btn-danger.active, -.btn-danger:active, -.show>.btn-danger.dropdown-toggle { - color: var(--md-sys-color-on-error); - background-color: var(--md-sys-color-error); - border-color: var(--md-sys-color-error); - box-shadow: var(--md-sys-elevation-3) !important; -} - -.btn-check:focus+.btn-danger, -.btn-danger:focus { - color: var(--md-sys-color-on-error); - background-color: var(--md-sys-color-error); - border-color: var(--md-sys-color-error); - box-shadow: var(--md-sys-elevation-3) !important; -} - -/* Info btn */ -.btn-info { - color: var(--md-sys-color-on-tertiary); - background-color: var(--md-sys-color-tertiary); - border-color: var(--md-sys-color-tertiary); - box-shadow: none !important; -} - -.btn-info .disabled, -.btn-info:disabled { - color: var(--md-sys-color-on-tertiary); - background-color: var(--md-sys-color-tertiary); - border-color: var(--md-sys-color-tertiary); -} - -.btn-info:hover { - color: var(--md-sys-color-on-tertiary); - background-color: var(--md-sys-color-tertiary); - border-color: var(--md-sys-color-tertiary); - box-shadow: var(--md-sys-elevation-3) !important; -} - -.btn-check:active+.btn-info, -.btn-check:checked+.btn-info, -.btn-info .active, -.btn-info:active, -.show>.btn-info.dropdown-toggle { - color: var(--md-sys-color-on-tertiary); - background-color: var(--md-sys-color-tertiary); - border-color: var(--md-sys-color-tertiary); - box-shadow: var(--md-sys-elevation-3) !important; -} - -.btn-check:focus+.btn-info, -.btn-info:focus { - color: var(--md-sys-color-on-tertiary); - background-color: var(--md-sys-color-tertiary); - border-color: var(--md-sys-color-tertiary); - box-shadow: var(--md-sys-elevation-3) !important; -} - -/* Info btn */ -.btn-success { - box-shadow: none !important; -} - -.btn-success:hover, -.btn-check:active+.btn-success, -.btn-check:checked+.btn-success, -.btn-success .active, -.btn-success:active, -.show>.btn-success.dropdown-toggle, -.btn-check:focus+.btn-success, -.btn-success:focus { - box-shadow: var(--md-sys-elevation-3) !important; -} - -/* Warning btn */ -.btn-warning { - box-shadow: none !important; -} - -.btn-warning:hover, -.btn-check:active+.btn-warning, -.btn-check:checked+.btn-warning, -.btn-warning .active, -.btn-warning:active, -.show>.btn-warning.dropdown-toggle, -.btn-check:focus+.btn-warning, -.btn-warning:focus { - box-shadow: var(--md-sys-elevation-3) !important; -} - -/* Outline Primary btn */ -.btn-outline-primary { - color: var(--md-sys-color-primary); - background-color: transparent; - border-color: var(--md-sys-color-primary); - box-shadow: none !important; -} - -.btn-outline-primary .disabled, -.btn-outline-primary:disabled { - color: var(--md-sys-color-primary); - background-color: transparent; - border-color: var(--md-sys-color-primary); -} - -.btn-outline-primary:hover { - color: var(--md-sys-color-on-primary); - background-color: var(--md-sys-color-primary); - border-color: var(--md-sys-color-primary); -} - -.btn-check:active+.btn-outline-primary, -.btn-check:checked+.btn-outline-primary, -.btn-outline-primary .active, -.btn-outline-primary:active, -.show>.btn-outline-primary.dropdown-toggle { - color: var(--md-sys-color-on-primary); - background-color: var(--md-sys-color-primary); - border-color: var(--md-sys-color-primary); -} - -.btn-check:focus+.btn-outline-primary, -.btn-outline-primary:focus { - color: var(--md-sys-color-on-primary); - background-color: var(--md-sys-color-primary); - border-color: var(--md-sys-color-primary); -} - -/* Outline Secondary btn */ -.btn-outline-secondary { - color: var(--md-sys-color-secondary); - background-color: transparent; - border-color: var(--md-sys-color-secondary); - box-shadow: none !important; -} - -.btn-outline-secondary .disabled, -.btn-outline-secondary:disabled { - color: var(--md-sys-color-secondary); - background-color: transparent; - border-color: var(--md-sys-color-secondary); -} - -.btn-outline-secondary:hover { - color: var(--md-sys-color-on-secondary); - background-color: var(--md-sys-color-secondary); - border-color: var(--md-sys-color-secondary); -} - -.btn-check:active+.btn-outline-secondary, -.btn-check:checked+.btn-outline-secondary, -.btn-outline-secondary .active, -.btn-outline-secondary:active, -.show>.btn-outline-secondary.dropdown-toggle { - color: var(--md-sys-color-on-secondary); - background-color: var(--md-sys-color-secondary); - border-color: var(--md-sys-color-secondary); -} - -.btn-check:focus+.btn-outline-secondary, -.btn-outline-secondary:focus { - color: var(--md-sys-color-on-secondary); - background-color: var(--md-sys-color-secondary); - border-color: var(--md-sys-color-secondary); -} - -/* Disabled btn */ -.btn.disabled, -.btn:disabled, -fieldset:disabled .btn { - pointer-events: none; - opacity: 0.65; -} - -/* Range Slider */ -.form-range{ - margin-top: 0.25rem; -} -.form-range:focus::-webkit-slider-thumb { - box-shadow: 0 0 0 1px var(--md-sys-color-surface), 0 0 0 .25rem var(--md-sys-color-primary) -} - -.form-range:focus::-moz-range-thumb { - box-shadow: 0 0 0 1px var(--md-sys-color-surface), 0 0 0 .25rem var(--md-sys-color-primary) -} - -.form-range::-webkit-slider-thumb { - background-color: var(--md-sys-color-primary); -} - - -.form-range::-webkit-slider-thumb:active { - background-color: var(--md-sys-color-primary) -} - -.form-range::-webkit-slider-runnable-track { - background-color: var(--md-sys-color-on-primary) -} - -.form-range::-moz-range-thumb { - background-color: var(--md-sys-color-primary); -} - - -/* checkbox */ -.form-check { - margin-bottom: 1rem; -} - -.form-check-label { - margin-left: 0.5rem; - margin-right: 0.5rem; -} - -.form-check-input { - width: 1.5rem; - height: 1.5rem; - margin: 0; - background-color: var(--md-sys-color-surface); - border: 2px solid var(--md-sys-color-outline-variant); -} - -.form-check-input:checked { - background-color: var(--md-sys-color-primary); - border-color: var(--md-sys-color-outline-variant); - border: none; -} - -.form-check-input:focus { - border-color: var(--md-sys-color-outline-variant); - outline: 0; - box-shadow: 0 0 0 .25rem var(--md-sys-color-outline-variant); -} - -.form-check-input:checked[type=checkbox] { - background-image: none; -} - -.form-check input[type="checkbox"]:checked+span.material-symbols-rounded { - display: block; -} - -.form-check span.material-symbols-rounded { - display: none; - color: var(--md-sys-color-surface); - position: absolute; - margin-left: -1.5rem; - margin-right: -1.5rem; - pointer-events: none; - user-select: none; - -webkit-user-select: none; - -webkit-tap-highlight-color: rgb(0 0 0 / 0%); -} - -.form-check { - min-height: 22px; - padding-left: 0; -} - -.form-check > label { - padding-left: 29px !important; - min-height: 22px; - line-height: 22px; - display: inline-block; - position: relative; - vertical-align: top; - margin-bottom: 0; - font-weight: normal; - cursor: pointer; - padding-right: 29px !important; -} - -.form-check > input:first-child { - position: absolute !important; - opacity: 0; - margin: 0; - background-color: var(--md-sys-state-hover-opacity); - border-radius: 50%; - appearance: none; - -moz-appearance: none; - -webkit-appearance: none; - -ms-appearance: none; - display: block; - width: 22px; - height: 22px; - outline: none; - transform: scale(1.65); - -ms-transform: scale(1.65); - transition: opacity .3s; -} - -.form-check > input:first-child:hover { - opacity: 1; - transform: scale(1.65); - -ms-transform: scale(1.65); -} - -.form-check > input:first-child:disabled { - cursor: default; -} - -.form-check > input:first-child:disabled + label, -.form-check > input:first-child:disabled + input[type="hidden"] + label, -.form-check > input:first-child:disabled + label::before, -.form-check > input:first-child:disabled + input[type="hidden"] + label::before { - pointer-events: none; - cursor: default; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - box-shadow: none; - opacity: .65; -} - -.form-check > input:first-child + label::before, -.form-check > input:first-child + input[type="hidden"] + label::before { - content: ""; - display: inline-block; - position: absolute; - width: 22px; - height: 22px; - border: 2px solid var(--md-sys-color-on-surface-variant); - border-radius: 3px; - margin-left: -29px; - box-sizing: border-box; - margin-right: -29px; -} - -.form-check > input:first-child:checked + label::after, -.form-check > input:first-child:checked + input[type="hidden"] + label::after { - content: ""; - display: inline-block; - position: absolute; - top: 0; - left: 0; - width: 7px; - height: 10px; - border: solid 2px; - border-left: none; - border-top: none; - transform: translate(7.75px, 4.5px) rotate(45deg); - -ms-transform: translate(7.75px, 4.5px) rotate(45deg); - box-sizing: border-box; - right: 0; - margin-right: 14px; - border-bottom-color: var(--md-sys-color-on-primary); - border-right-color: var(--md-sys-color-on-primary); -} - -.form-check > input:first-child::-ms-check { - opacity: 0; - border-radius: 50%; - background-color: var(--md-sys-color-primary); -} - -.form-check > input:first-child:active { - transform: scale(0); - -ms-transform: scale(0); - opacity: 1; - transition: opacity 0s, transform 0s; -} - -.form-check > input[type="radio"]:first-child + label::before, -.form-check > input[type="radio"]:first-child + input[type="hidden"] + label::before { - border-radius: 50%; -} - -.form-check > input[type="radio"]:first-child:checked + label::before, -.form-check > input[type="radio"]:first-child:checked + input[type="hidden"] + label::before { - background-color: transparent; -} - -.form-check > input[type="radio"]:first-child:checked + label::after, -.form-check > input[type="radio"]:first-child:checked + input[type="hidden"] + label::after { - content: ""; - position: absolute; - width: 10px; - height: 10px; - border-radius: 50%; - border: none; - top: 6px; - left: 6px; - transform: none; - -ms-transform: none; -} - -.form-check > input[type="checkbox"]:first-child:checked + label::after, -.form-check > input[type="checkbox"]:first-child:checked + input[type="hidden"] + label::after { - width: 8px; - height: 14px; - transform: translate(7px, 2px) rotate(45deg); - -ms-transform: translate(7px, 2px) rotate(45deg); -} - -.form-check-inline { - display: inline-block; -} - -.form-check-inline + .form-check-inline { - margin-left: .75rem; - margin-top: 6px; -} - -.form-check > input:first-child:checked + label::before, -.form-check > input:first-child:checked + input[type="hidden"] + label::before { - background-color: var(--md-sys-color-primary); - border-color: var(--md-sys-color-primary); -} - -/* Forms */ -textarea.form-control { - border-radius: 1.5rem !important; -} - -.form-control, -.form-select, -.form-control:disabled, -.form-control[readonly] { - color: var(--md-sys-color-on-surface); - background-color: var(--md-sys-color-surface-container-low); - border-color: var(--md-sys-color-outline-variant); - border-radius: 3rem !important; -} - -.form-control:focus, -.form-select:focus { - color: var(--md-sys-color-on-surface); - background-color: var(--md-sys-color-surface-container-lowest); - border-color: var(--md-sys-color-outline-variant); - outline: 0; - box-shadow: 0 0 0 0.25rem var(--md-sys-color-outline-variant); -} - -.form-control-color { - padding: 0; - height: 2.4rem; - width: 2.4rem; -} - -.form-control input[type="color"] { - opacity: 0; - height: 2.4rem; - width: 2.4rem; - box-sizing: border-box; - } - - .form-control input[type="color"]:hover{ - cursor: pointer; - } - -/* Navbar Components */ -.navbar-brand { - color: var(--md-sys-color-on-surface) !important; -} - -.nav-link { - display: flex; - align-items: center; - transition: none !important; - padding: 0.5rem 1rem !important; - border: 1px transparent; -} - -.navbar-nav li { - flex: 1; -} - -.navbar-nav .nav-link { - color: var(--md-sys-color-on-surface-variant); -} - -.navbar-nav .nav-link:focus, -.navbar-nav .nav-link:hover { - color: var(--md-sys-color-on-secondary-container); - background-color: var(--md-sys-color-surface-3); - border-radius: 3rem; - font-weight: 500; - font-variation-settings: var(--md-sys-icon-fill-1); -} - -.navbar-nav .nav-link.active, -.navbar-nav .show>.nav-link { - color: var(--md-sys-color-on-secondary-container); - background-color: var(--md-sys-color-surface-5); - border-radius: 3rem; - font-weight: 500; - font-variation-settings: var(--md-sys-icon-fill-1); -} - -.menu-title { - padding: 0 1rem; -} - -.dropdown-menu { - margin: 0 1%; - padding: 1.5rem 0; - border-radius: 1rem; - min-width: 20rem; - color: var(--md-sys-color-on-surface); - background-color: var(--md-sys-color-surface-container); - border: 1px solid var(--md-sys-color-surface-5); - box-shadow: var(--md-sys-elevation-2); -} - -.dropdown-item { - color: var(--md-sys-color-on-surface); - padding: 0.25rem 1rem; - border-radius: 3rem; - -} - -.dropdown-item:focus, -.dropdown-item:hover { - color: var(--md-sys-color-on-surface); - background-color: var(--md-sys-color-surface-5); - border-radius: 3rem; - font-variation-settings: var(--md-sys-icon-fill-1); -} -.dropdown-item.no-hover:hover, -.dropdown-item.no-hover:focus { - color: var(--md-sys-color-on-surface) !important; - background-color: transparent !important; - border-radius: 3rem !important; - font-variation-settings: initial !important; -} - -.dropdown-item.active, -.dropdown-item:active { - color: var(--md-sys-color-on-surface); - background-color: var(--md-sys-color-surface-5); - border-radius: 3rem; - font-weight: 500; - font-variation-settings: var(--md-sys-icon-fill-1); -} - -/* list-group-item */ -.list-group-item { - color: var(--md-sys-color-on-surface); - background-color: var(--md-sys-color-surface-5); - border: 1px solid var(--md-sys-color-outline-variant); -} - -.list-group-item:first-child { - border-radius: 1rem 1rem 0rem 0rem; -} - -.list-group-item:last-child { - border-radius: 0rem 0rem 1rem 1rem; -} - -.list-group-item:only-child { - border-radius: 1rem 1rem 1rem 1rem; -} - -.list-group-item .btn { - padding: .375rem .5rem; -} - -/*Alert */ -.alert-container { - padding: 2rem 3rem; - border-radius: 3rem; - margin: 1rem 0rem 2rem; -} - -.alert-header { - display: flex !important; - justify-content: space-between; -} - -.alert-heading { - font-size: calc(1.275rem + .3vw); -} - -.alert-dismissible .btn-close { - position: relative; - padding: 0; -} - -.alert-danger { - color: var(--md-sys-color-on-error-container); - background-color: var(--md-sys-color-error-container); - border-color: transparent; -} diff --git a/app/core/src/main/resources/static/css/theme/font.css b/app/core/src/main/resources/static/css/theme/font.css deleted file mode 100644 index a90b14a900..0000000000 --- a/app/core/src/main/resources/static/css/theme/font.css +++ /dev/null @@ -1,24 +0,0 @@ -@font-face { - font-family: 'Material Symbols Rounded'; - font-style: normal; - font-weight: 100 700; - src: url(../../fonts/google-symbol.woff2) format('woff2'); -} - - - -.material-symbols-rounded { - font-family: 'Material Symbols Rounded'; - font-weight: 300; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} diff --git a/app/core/src/main/resources/static/css/theme/theme.css b/app/core/src/main/resources/static/css/theme/theme.css deleted file mode 100644 index 70958b0eb2..0000000000 --- a/app/core/src/main/resources/static/css/theme/theme.css +++ /dev/null @@ -1,42 +0,0 @@ -:where(html, .light-theme, .dark-theme), -.tokens, -:host { - /* Define surface colors based on primary color */ - --md-sys-color-surface-1: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 100, 0.05) 5%); - --md-sys-color-surface-2: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 255, 0.08) 5%); - --md-sys-color-surface-3: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 255, 0.11) 5%); - --md-sys-color-surface-4: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 255, 0.12) 5%); - --md-sys-color-surface-5: color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 255, 0.14) 5%); - /* Clear button disabled text color (default/light) */ - --spdf-clear-disabled-text: var(--md-sys-color-primary); - /* Icon fill */ - --md-sys-icon-fill-0: 'FILL' 0, 'wght' 500; - --md-sys-icon-fill-1: 'FILL' 1, 'wght' 500; - /* Hover Color */ - --md-sys-state-hover-opacity: color-mix(in srgb, var(--md-sys-color-primary), rgba(0, 0, 0, 0) 80%); - /* Shadow */ - --md-sys-color-shadow: #000000; - --md-elevation-shadow-color-rgb: 0, 0, 0; - --md-elevation-shadow-color: var(--md-elevation-shadow-color-rgb); - /* Shadow Elevation*/ - --md-sys-elevation-0: 0px 0px 0px 0px rgb(var(--md-elevation-shadow-color), 0.2), 0px 0px 0px 0px rgb(var(--md-elevation-shadow-color), 0.14), 0px 0px 0px 0px rgb(var(--md-elevation-shadow-color), 0.12); - --md-sys-elevation-1: 0px 3px 1px -2px rgb(var(--md-elevation-shadow-color), 0.2), 0px 2px 2px 0px rgb(var(--md-elevation-shadow-color), 0.14), 0px 1px 5px 0px rgb(var(--md-elevation-shadow-color), 0.12); - --md-sys-elevation-2: 0px 2px 4px -1px rgb(var(--md-elevation-shadow-color), 0.2), 0px 4px 5px 0px rgb(var(--md-elevation-shadow-color), 0.14), 0px 1px 10px 0px rgb(var(--md-elevation-shadow-color), 0.12); - --md-sys-elevation-3: 0px 5px 5px -3px rgb(var(--md-elevation-shadow-color), 0.2), 0px 8px 10px 1px rgb(var(--md-elevation-shadow-color), 0.14), 0px 3px 14px 2px rgb(var(--md-elevation-shadow-color), 0.12); - --md-sys-elevation-4: 0px 5px 5px -3px rgb(var(--md-elevation-shadow-color) / 0.2), 0px 8px 10px 1px rgb(var(--md-elevation-shadow-color), 0.14), 0px 3px 14px 2px rgb(var(--md-elevation-shadow-color), 0.12); - --md-sys-elevation-5: 0px 8px 10px -6px rgb(var(--md-elevation-shadow-color), 0.2), 0px 16px 24px 2px rgb(var(--md-elevation-shadow-color), 0.14), 0px 6px 30px 5px rgb(var(--md-elevation-shadow-color), 0.12); -} - -/* Dark theme overrides */ -.dark-theme { - /* In dark mode, use a neutral grey for disabled Clear button text */ - --spdf-clear-disabled-text: var(--mantine-color-gray-5, #9e9e9e); -} - -.fill { - font-variation-settings: var(--md-sys-icon-fill-1); -} - -.no-fill { - /* font-variation-settings: var(--md-sys-icon-fill-0); */ -} diff --git a/app/core/src/main/resources/static/css/theme/theme.dark.css b/app/core/src/main/resources/static/css/theme/theme.dark.css deleted file mode 100644 index 411c8f14c2..0000000000 --- a/app/core/src/main/resources/static/css/theme/theme.dark.css +++ /dev/null @@ -1,79 +0,0 @@ -:root { - /* Colors */ - --md-sys-color-primary: rgb(162 201 255); - --md-sys-color-surface-tint: rgb(162 201 255); - --md-sys-color-on-primary: rgb(0 49 92); - --md-sys-color-primary-container: rgb(0 118 208); - --md-sys-color-on-primary-container: rgb(255 255 255); - --md-sys-color-secondary: rgb(169 201 246); - --md-sys-color-on-secondary: rgb(12 49 87); - --md-sys-color-secondary-container: rgb(29 62 100); - --md-sys-color-on-secondary-container: rgb(180 210 255); - --md-sys-color-tertiary: rgb(193 194 248); - --md-sys-color-on-tertiary: rgb(42 44 88); - --md-sys-color-tertiary-container: rgb(110 112 161); - --md-sys-color-on-tertiary-container: rgb(255 255 255); - --md-sys-color-error: rgb(255 180 171); - --md-sys-color-on-error: rgb(105 0 5); - --md-sys-color-error-container: rgb(147 0 10); - --md-sys-color-on-error-container: rgb(255 218 214); - --md-sys-color-background: rgb(15 20 26); - --md-sys-color-on-background: rgb(223 226 235); - --md-sys-color-surface: rgb(15 20 26); - --md-sys-color-on-surface: rgb(223 226 235); - --md-sys-color-surface-variant: rgb(64 71 83); - --md-sys-color-on-surface-variant: rgb(192 199 213); - --md-sys-color-outline: rgb(138 145 158); - --md-sys-color-outline-variant: rgb(64 71 83); - --md-sys-color-shadow: rgb(0 0 0); - --md-sys-color-scrim: rgb(0 0 0); - --md-sys-color-inverse-surface: rgb(223 226 235); - --md-sys-color-inverse-on-surface: rgb(45 49 55); - --md-sys-color-inverse-primary: rgb(0 96 170); - --md-sys-color-primary-fixed: rgb(211 228 255); - --md-sys-color-on-primary-fixed: rgb(0 28 56); - --md-sys-color-primary-fixed-dim: rgb(162 201 255); - --md-sys-color-on-primary-fixed-variant: rgb(0 72 130); - --md-sys-color-secondary-fixed: rgb(211 228 255); - --md-sys-color-on-secondary-fixed: rgb(0 28 56); - --md-sys-color-secondary-fixed-dim: rgb(169 201 246); - --md-sys-color-on-secondary-fixed-variant: rgb(40 72 111); - --md-sys-color-tertiary-fixed: rgb(225 224 255); - --md-sys-color-on-tertiary-fixed: rgb(20 22 66); - --md-sys-color-tertiary-fixed-dim: rgb(193 194 248); - --md-sys-color-on-tertiary-fixed-variant: rgb(64 67 112); - --md-sys-color-surface-dim: rgb(15 20 26); - --md-sys-color-surface-bright: rgb(53 57 64); - --md-sys-color-surface-container-lowest: rgb(10 14 20); - --md-sys-color-surface-container-low: rgb(24 28 34); - --md-sys-color-surface-container: rgb(28 32 38); - --md-sys-color-surface-container-high: rgb(38 42 49); - --md-sys-color-surface-container-highest: rgb(49 53 60); - /* Tools Color */ - --md-nav-section-color-opacity: 1; - --md-nav-on-section-color-opacity: 1; - --md-nav-section-color-sign: rgba(25, 101, 212, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-sign: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-organize: rgba(120, 130, 255, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-organize: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-convert: rgba(25, 177, 212, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-convert: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-convertto: rgba(104, 220, 149, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-convertto: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-security: rgba(255, 120, 146, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-security: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-other: rgba(72, 189, 84, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-other: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-advance: rgba(245, 84, 84, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-advance: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-image: rgba(212, 172, 25, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-image: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-word: rgba(61, 153, 245, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-word: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-ppt: rgba(255, 128, 0, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-ppt: rgba(28, 27, 31, var(--md-nav-on-section-color-opacity)); - --md-nav-color-on-separator: rgb(24 28 34); - --md-nav-background: rgb(15 20 26); - --favourite-add: #9ed18c; - --favourite-remove: palevioletred; -} \ No newline at end of file diff --git a/app/core/src/main/resources/static/css/theme/theme.light.css b/app/core/src/main/resources/static/css/theme/theme.light.css deleted file mode 100644 index f4bfc40656..0000000000 --- a/app/core/src/main/resources/static/css/theme/theme.light.css +++ /dev/null @@ -1,79 +0,0 @@ -:root { - /* Colors */ - --md-sys-color-primary: rgb(0 96 170); - --md-sys-color-surface-tint: rgb(0 96 170); - --md-sys-color-on-primary: rgb(255 255 255); - --md-sys-color-primary-container: rgb(80 163 255); - --md-sys-color-on-primary-container: rgb(0 20 43); - --md-sys-color-secondary: rgb(65 96 136); - --md-sys-color-on-secondary: rgb(255 255 255); - --md-sys-color-secondary-container: rgb(188 215 255); - --md-sys-color-on-secondary-container: rgb(32 65 103); - --md-sys-color-tertiary: rgb(88 90 138); - --md-sys-color-on-tertiary: rgb(255 255 255); - --md-sys-color-tertiary-container: rgb(151 153 205); - --md-sys-color-on-tertiary-container: rgb(7 9 55); - --md-sys-color-error: rgb(186 26 26); - --md-sys-color-on-error: rgb(255 255 255); - --md-sys-color-error-container: rgb(255 218 214); - --md-sys-color-on-error-container: rgb(65 0 2); - --md-sys-color-background: rgb(248 249 255); - --md-sys-color-on-background: rgb(24 28 34); - --md-sys-color-surface: rgb(237, 240, 245); - --md-sys-color-on-surface: rgb(0, 1, 1); - --md-sys-color-surface-variant: rgb(220 227 241); - --md-sys-color-on-surface-variant: rgb(64 71 83); - --md-sys-color-outline: rgb(112 119 132); - --md-sys-color-outline-variant: rgb(192 199 213); - --md-sys-color-shadow: rgb(0 0 0); - --md-sys-color-scrim: rgb(0 0 0); - --md-sys-color-inverse-surface: rgb(45 49 55); - --md-sys-color-inverse-on-surface: rgb(238 241 250); - --md-sys-color-inverse-primary: rgb(162 201 255); - --md-sys-color-primary-fixed: rgb(211 228 255); - --md-sys-color-on-primary-fixed: rgb(0 28 56); - --md-sys-color-primary-fixed-dim: rgb(162 201 255); - --md-sys-color-on-primary-fixed-variant: rgb(0 72 130); - --md-sys-color-secondary-fixed: rgb(211 228 255); - --md-sys-color-on-secondary-fixed: rgb(0 28 56); - --md-sys-color-secondary-fixed-dim: rgb(169 201 246); - --md-sys-color-on-secondary-fixed-variant: rgb(40 72 111); - --md-sys-color-tertiary-fixed: rgb(225 224 255); - --md-sys-color-on-tertiary-fixed: rgb(20 22 66); - --md-sys-color-tertiary-fixed-dim: rgb(193 194 248); - --md-sys-color-on-tertiary-fixed-variant: rgb(64 67 112); - --md-sys-color-surface-dim: rgb(215 218 227); - --md-sys-color-surface-bright: rgb(248 249 255); - --md-sys-color-surface-container-lowest: rgb(255 255 255); - --md-sys-color-surface-container-low: rgb(241 243 253); - --md-sys-color-surface-container: rgb(235 238 247); - --md-sys-color-surface-container-high: rgb(229 232 241); - --md-sys-color-surface-container-highest: rgb(223 226 235); - /* Tools Color */ - --md-nav-section-color-opacity: 1; - --md-nav-on-section-color-opacity: 1; - --md-nav-section-color-sign: rgba(25, 101, 212, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-sign: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-organize: rgba(120, 130, 255, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-organize: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-convert: rgba(25, 177, 212, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-convert: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-convertto: rgba(104, 220, 149, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-convertto: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-security: rgba(255, 120, 146, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-security: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-other: rgba(72, 189, 84, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-other: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-advance: rgba(245, 84, 84, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-advance: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-image: rgba(212, 172, 25, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-image: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-word: rgba(61, 153, 245, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-word: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); - --md-nav-section-color-ppt: rgba(255, 128, 0, var(--md-nav-section-color-opacity)); - --md-nav-on-section-color-ppt: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity)); - --md-nav-color-on-separator: rgb(174, 178, 179); - --md-nav-background: rgb(248 249 255); - --favourite-add: #25ab6c; - --favourite-remove: rgb(222, 94, 137); -} \ No newline at end of file diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/RearrangePagesPDFControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/RearrangePagesPDFControllerTest.java deleted file mode 100644 index e4fa16d706..0000000000 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/RearrangePagesPDFControllerTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package stirling.software.SPDF.controller.api; - -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import stirling.software.common.service.CustomPDFDocumentFactory; - -@ExtendWith({MockitoExtension.class}) -class RearrangePagesPDFControllerTest { - - @Mock private CustomPDFDocumentFactory mockPdfDocumentFactory; - - private RearrangePagesPDFController sut; - - @BeforeEach - void setUp() { - sut = new RearrangePagesPDFController(mockPdfDocumentFactory); - } - - /** Tests the behavior of the oddEvenMerge method when there are no pages in the document. */ - @Test - void oddEvenMerge_noPages() { - int totalNumberOfPages = 0; - - List newPageOrder = sut.oddEvenMerge(totalNumberOfPages); - - assertNotNull(newPageOrder, "Returning null instead of page order list"); - assertEquals(List.of(), newPageOrder, "Page order doesn't match"); - } - - /** - * Tests the behavior of the oddEvenMerge method when there are odd total pages in the document. - */ - @Test - void oddEvenMerge_oddTotalPageNumber() { - int totalNumberOfPages = 5; - - List newPageOrder = sut.oddEvenMerge(totalNumberOfPages); - - assertNotNull(newPageOrder, "Returning null instead of page order list"); - assertEquals(Arrays.asList(0, 3, 1, 4, 2), newPageOrder, "Page order doesn't match"); - } - - /** - * Tests the behavior of the oddEvenMerge method when there are even total pages in the - * document. - */ - @Test - void oddEvenMerge_evenTotalPageNumber() { - int totalNumberOfPages = 6; - - List newPageOrder = sut.oddEvenMerge(totalNumberOfPages); - - assertNotNull(newPageOrder, "Returning null instead of page order list"); - assertEquals(Arrays.asList(0, 3, 1, 4, 2, 5), newPageOrder, "Page order doesn't match"); - } - - /** - * Tests the behavior of the oddEvenMerge method with multiple test cases of multiple pages. - * - * @param totalNumberOfPages The total number of pages in the document. - * @param expectedPageOrder The expected order of the pages after rearranging. - */ - @ParameterizedTest - @CsvSource({ - "1, '0'", - "2, '0,1'", - "3, '0,2,1'", - "4, '0,2,1,3'", - "5, '0,3,1,4,2'", - "6, '0,3,1,4,2,5'", - "10, '0,5,1,6,2,7,3,8,4,9'", - "50, '0,25,1,26,2,27,3,28,4,29,5,30,6,31,7,32,8,33,9,34,10,35," - + "11,36,12,37,13,38,14,39,15,40,16,41,17,42,18,43,19,44,20,45,21,46," - + "22,47,23,48,24,49'" - }) - void oddEvenMerge_multi_test(int totalNumberOfPages, String expectedPageOrder) { - List newPageOrder = sut.oddEvenMerge(totalNumberOfPages); - - assertNotNull(newPageOrder, "Returning null instead of page order list"); - assertEquals( - Arrays.stream(expectedPageOrder.split(",")).map(Integer::parseInt).toList(), - newPageOrder, - "Page order doesn't match"); - } -} diff --git a/app/core/src/test/java/stirling/software/SPDF/model/SortTypesTest.java b/app/core/src/test/java/stirling/software/SPDF/model/SortTypesTest.java index 87fe933053..4b231e4e27 100644 --- a/app/core/src/test/java/stirling/software/SPDF/model/SortTypesTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/model/SortTypesTest.java @@ -20,7 +20,6 @@ class SortTypesTest { "BOOKLET_SORT", "SIDE_STITCH_BOOKLET_SORT", "ODD_EVEN_SPLIT", - "ODD_EVEN_MERGE", "REMOVE_FIRST", "REMOVE_LAST", "REMOVE_FIRST_AND_LAST", diff --git a/app/core/src/test/java/stirling/software/SPDF/service/PdfImageRemovalServiceTest.java b/app/core/src/test/java/stirling/software/SPDF/service/PdfImageRemovalServiceTest.java deleted file mode 100644 index f237f2f0f6..0000000000 --- a/app/core/src/test/java/stirling/software/SPDF/service/PdfImageRemovalServiceTest.java +++ /dev/null @@ -1,146 +0,0 @@ -package stirling.software.SPDF.service; - -import static org.mockito.Mockito.*; - -import java.io.IOException; -import java.util.*; - -import org.apache.pdfbox.cos.COSName; -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.graphics.PDXObject; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -class PdfImageRemovalServiceTest { - - private PdfImageRemovalService service; - - @BeforeEach - void setUp() { - service = new PdfImageRemovalService(); - } - - // Helper method for matching COSName in verification - private static COSName eq(final COSName value) { - return Mockito.argThat( - new org.mockito.ArgumentMatcher<>() { - @Override - public boolean matches(COSName argument) { - if (argument == null && value == null) return true; - if (argument == null || value == null) return false; - return argument.getName().equals(value.getName()); - } - - @Override - public String toString() { - return "eq(" + (value != null ? value.getName() : "null") + ")"; - } - }); - } - - @Test - void testRemoveImagesFromPdf_WithImages() throws IOException { - // Mock PDF document and its components - PDDocument document = mock(PDDocument.class); - PDPage page = mock(PDPage.class); - PDResources resources = mock(PDResources.class); - PDPageTree pageTree = mock(PDPageTree.class); - - // Configure page tree to iterate over our single page - when(document.getPages()).thenReturn(pageTree); - Iterator pageIterator = Collections.singletonList(page).iterator(); - when(pageTree.iterator()).thenReturn(pageIterator); - - // Set up page resources - when(page.getResources()).thenReturn(resources); - - // Set up image XObjects - COSName img1 = COSName.getPDFName("Im1"); - COSName img2 = COSName.getPDFName("Im2"); - COSName nonImg = COSName.getPDFName("NonImg"); - - List xObjectNames = Arrays.asList(img1, img2, nonImg); - when(resources.getXObjectNames()).thenReturn(xObjectNames); - - // Configure which are image XObjects - when(resources.isImageXObject(img1)).thenReturn(true); - when(resources.isImageXObject(img2)).thenReturn(true); - when(resources.isImageXObject(nonImg)).thenReturn(false); - - // Execute the method - service.removeImagesFromPdf(document); - - // Verify that images were removed - verify(resources, times(1)).put(eq(img1), Mockito.isNull()); - verify(resources, times(1)).put(eq(img2), Mockito.isNull()); - verify(resources, never()).put(eq(nonImg), Mockito.isNull()); - } - - @Test - void testRemoveImagesFromPdf_NoImages() throws IOException { - // Mock PDF document and its components - PDDocument document = mock(PDDocument.class); - PDPage page = mock(PDPage.class); - PDResources resources = mock(PDResources.class); - PDPageTree pageTree = mock(PDPageTree.class); - - // Configure page tree to iterate over our single page - when(document.getPages()).thenReturn(pageTree); - Iterator pageIterator = Collections.singletonList(page).iterator(); - when(pageTree.iterator()).thenReturn(pageIterator); - - // Set up page resources - when(page.getResources()).thenReturn(resources); - - // Create empty list of XObject names - List emptyList = new ArrayList<>(); - when(resources.getXObjectNames()).thenReturn(emptyList); - - // Execute the method - service.removeImagesFromPdf(document); - - // Verify that no modifications were made - verify(resources, never()).put(any(COSName.class), any(PDXObject.class)); - } - - @Test - void testRemoveImagesFromPdf_MultiplePages() throws IOException { - // Mock PDF document and its components - PDDocument document = mock(PDDocument.class); - PDPage page1 = mock(PDPage.class); - PDPage page2 = mock(PDPage.class); - PDResources resources1 = mock(PDResources.class); - PDResources resources2 = mock(PDResources.class); - PDPageTree pageTree = mock(PDPageTree.class); - - // Configure page tree to iterate over our two pages - when(document.getPages()).thenReturn(pageTree); - Iterator pageIterator = Arrays.asList(page1, page2).iterator(); - when(pageTree.iterator()).thenReturn(pageIterator); - - // Set up page resources - when(page1.getResources()).thenReturn(resources1); - when(page2.getResources()).thenReturn(resources2); - - // Set up image XObjects for page 1 - COSName img1 = COSName.getPDFName("Im1"); - when(resources1.getXObjectNames()).thenReturn(Collections.singletonList(img1)); - when(resources1.isImageXObject(img1)).thenReturn(true); - - // Set up image XObjects for page 2 - COSName img2 = COSName.getPDFName("Im2"); - when(resources2.getXObjectNames()).thenReturn(Collections.singletonList(img2)); - when(resources2.isImageXObject(img2)).thenReturn(true); - - // Execute the method - service.removeImagesFromPdf(document); - - // Verify that images were removed from both pages - verify(resources1, times(1)).put(eq(img1), Mockito.isNull()); - verify(resources2, times(1)).put(eq(img2), Mockito.isNull()); - } -}