mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01:00 
			
		
		
		
	Feature/save signs (#2127)
* apply fix * Fixes empty th:action * Update build.gradle * fix * formatting * Save signatures * Fix code scanning alert no. 42: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix UserServiceInterface * Merge branch 'feature/saveSigns' of git@github.com:Stirling-Tools/Stirling-PDF.git into feature/saveSigns * 0.31.0 bump and further csrf * formatting * preview name * add * sign doc * Update translation files (#2128) Signed-off-by: GitHub Action <action@github.com> Co-authored-by: GitHub Action <action@github.com> --------- Signed-off-by: GitHub Action <action@github.com> Co-authored-by: Dimitrios Kaitantzidis <james_k23@hotmail.gr> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: a <a> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
		
							parent
							
								
									ed75fa4e1b
								
							
						
					
					
						commit
						27d2681a97
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -4,6 +4,7 @@ bin/
 | 
			
		||||
tmp/
 | 
			
		||||
*.tmp
 | 
			
		||||
*.bak
 | 
			
		||||
*.exe
 | 
			
		||||
*.swp
 | 
			
		||||
*~.nib
 | 
			
		||||
local.properties
 | 
			
		||||
 | 
			
		||||
@ -166,6 +166,13 @@ Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "pod
 | 
			
		||||
 | 
			
		||||
Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR.md
 | 
			
		||||
 | 
			
		||||
## Reuse stored files
 | 
			
		||||
 | 
			
		||||
Certain functionality like ``Sign`` Supports pre-saved files stored at ``/customFiles/signatures/``, image files placed within here will be accesable to be used via webUI
 | 
			
		||||
Currently this supports two folder types
 | 
			
		||||
- ``/customFiles/signatures/ALL_USERS`` accessible to all users, useful for orginasations were many users use same files or for users not using authentication
 | 
			
		||||
- ``/customFiles/signatures/{username}`` such as ``/customFiles/signatures/froodle``  accessible to only the ``froodle`` username, private for all others
 | 
			
		||||
 | 
			
		||||
## Supported Languages
 | 
			
		||||
 | 
			
		||||
Stirling PDF currently supports 38!
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,7 @@ ext {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
group = "stirling.software"
 | 
			
		||||
version = "0.30.1"
 | 
			
		||||
version = "0.31.0"
 | 
			
		||||
 | 
			
		||||
java {
 | 
			
		||||
    // 17 is lowest but we support and recommend 21
 | 
			
		||||
 | 
			
		||||
@ -104,7 +104,32 @@ public class SecurityConfiguration {
 | 
			
		||||
                requestHandler.setCsrfRequestAttributeName(null);
 | 
			
		||||
                http.csrf(
 | 
			
		||||
                        csrf ->
 | 
			
		||||
                                csrf.csrfTokenRepository(cookieRepo)
 | 
			
		||||
                                csrf.ignoringRequestMatchers(
 | 
			
		||||
                                                request -> {
 | 
			
		||||
                                                    String apiKey = request.getHeader("X-API-Key");
 | 
			
		||||
 | 
			
		||||
                                                    // If there's no API key, don't ignore CSRF
 | 
			
		||||
                                                    // (return false)
 | 
			
		||||
                                                    if (apiKey == null || apiKey.trim().isEmpty()) {
 | 
			
		||||
                                                        return false;
 | 
			
		||||
                                                    }
 | 
			
		||||
 | 
			
		||||
                                                    // Validate API key using existing UserService
 | 
			
		||||
                                                    try {
 | 
			
		||||
                                                        Optional<User> user =
 | 
			
		||||
                                                                userService.getUserByApiKey(apiKey);
 | 
			
		||||
                                                        // If API key is valid, ignore CSRF (return
 | 
			
		||||
                                                        // true)
 | 
			
		||||
                                                        // If API key is invalid, don't ignore CSRF
 | 
			
		||||
                                                        // (return false)
 | 
			
		||||
                                                        return user.isPresent();
 | 
			
		||||
                                                    } catch (Exception e) {
 | 
			
		||||
                                                        // If there's any error validating the API
 | 
			
		||||
                                                        // key, don't ignore CSRF
 | 
			
		||||
                                                        return false;
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                })
 | 
			
		||||
                                        .csrfTokenRepository(cookieRepo)
 | 
			
		||||
                                        .csrfTokenRequestHandler(requestHandler));
 | 
			
		||||
            }
 | 
			
		||||
            http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
 | 
			
		||||
 | 
			
		||||
@ -96,17 +96,18 @@ public class CertSignController {
 | 
			
		||||
 | 
			
		||||
        public CreateSignature(KeyStore keystore, char[] pin)
 | 
			
		||||
                throws KeyStoreException,
 | 
			
		||||
                UnrecoverableKeyException,
 | 
			
		||||
                NoSuchAlgorithmException,
 | 
			
		||||
                IOException,
 | 
			
		||||
                CertificateException {
 | 
			
		||||
                        UnrecoverableKeyException,
 | 
			
		||||
                        NoSuchAlgorithmException,
 | 
			
		||||
                        IOException,
 | 
			
		||||
                        CertificateException {
 | 
			
		||||
            super(keystore, pin);
 | 
			
		||||
            ClassPathResource resource = new ClassPathResource("static/images/signature.png");
 | 
			
		||||
            imageFile = resource.getFile();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public InputStream createVisibleSignature(PDDocument srcDoc, PDSignature signature, Integer pageNumber,
 | 
			
		||||
                Boolean showImage) throws IOException {
 | 
			
		||||
        public InputStream createVisibleSignature(
 | 
			
		||||
                PDDocument srcDoc, PDSignature signature, Integer pageNumber, Boolean showImage)
 | 
			
		||||
                throws IOException {
 | 
			
		||||
            // modified from org.apache.pdfbox.examples.signature.CreateVisibleSignature2
 | 
			
		||||
            try (PDDocument doc = new PDDocument()) {
 | 
			
		||||
                PDPage page = new PDPage(srcDoc.getPage(pageNumber).getMediaBox());
 | 
			
		||||
@ -151,8 +152,8 @@ public class CertSignController {
 | 
			
		||||
                        extState.setNonStrokingAlphaConstant(0.5f);
 | 
			
		||||
                        cs.setGraphicsStateParameters(extState);
 | 
			
		||||
                        cs.transform(Matrix.getScaleInstance(0.08f, 0.08f));
 | 
			
		||||
                        PDImageXObject img = PDImageXObject.createFromFileByExtension(imageFile,
 | 
			
		||||
                                doc);
 | 
			
		||||
                        PDImageXObject img =
 | 
			
		||||
                                PDImageXObject.createFromFileByExtension(imageFile, doc);
 | 
			
		||||
                        cs.drawImage(img, 100, 0);
 | 
			
		||||
                        cs.restoreGraphicsState();
 | 
			
		||||
                    }
 | 
			
		||||
@ -200,7 +201,10 @@ public class CertSignController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PostMapping(consumes = "multipart/form-data", value = "/cert-sign")
 | 
			
		||||
    @Operation(summary = "Sign PDF with a Digital Certificate", description = "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO")
 | 
			
		||||
    @Operation(
 | 
			
		||||
            summary = "Sign PDF with a Digital Certificate",
 | 
			
		||||
            description =
 | 
			
		||||
                    "This endpoint accepts a PDF file, a digital certificate and related information to sign the PDF. It then returns the digitally signed PDF file. Input:PDF Output:PDF Type:SISO")
 | 
			
		||||
    public ResponseEntity<byte[]> signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
 | 
			
		||||
            throws Exception {
 | 
			
		||||
        MultipartFile pdf = request.getFileInput();
 | 
			
		||||
@ -229,7 +233,7 @@ public class CertSignController {
 | 
			
		||||
                PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password);
 | 
			
		||||
                Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes());
 | 
			
		||||
                ks.setKeyEntry(
 | 
			
		||||
                        "alias", privateKey, password.toCharArray(), new Certificate[] { cert });
 | 
			
		||||
                        "alias", privateKey, password.toCharArray(), new Certificate[] {cert});
 | 
			
		||||
                break;
 | 
			
		||||
            case "PKCS12":
 | 
			
		||||
                ks = KeyStore.getInstance("PKCS12");
 | 
			
		||||
@ -245,7 +249,15 @@ public class CertSignController {
 | 
			
		||||
 | 
			
		||||
        CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());
 | 
			
		||||
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
 | 
			
		||||
        sign(pdfDocumentFactory, pdf.getBytes(), baos, createSignature, showSignature, pageNumber, name, location,
 | 
			
		||||
        sign(
 | 
			
		||||
                pdfDocumentFactory,
 | 
			
		||||
                pdf.getBytes(),
 | 
			
		||||
                baos,
 | 
			
		||||
                createSignature,
 | 
			
		||||
                showSignature,
 | 
			
		||||
                pageNumber,
 | 
			
		||||
                name,
 | 
			
		||||
                location,
 | 
			
		||||
                reason);
 | 
			
		||||
        return WebResponseUtils.boasToWebResponse(
 | 
			
		||||
                baos,
 | 
			
		||||
@ -274,8 +286,8 @@ public class CertSignController {
 | 
			
		||||
 | 
			
		||||
            if (showSignature) {
 | 
			
		||||
                SignatureOptions signatureOptions = new SignatureOptions();
 | 
			
		||||
                signatureOptions
 | 
			
		||||
                        .setVisualSignature(instance.createVisibleSignature(doc, signature, pageNumber, true));
 | 
			
		||||
                signatureOptions.setVisualSignature(
 | 
			
		||||
                        instance.createVisibleSignature(doc, signature, pageNumber, true));
 | 
			
		||||
                signatureOptions.setPage(pageNumber);
 | 
			
		||||
 | 
			
		||||
                doc.addSignature(signature, instance, signatureOptions);
 | 
			
		||||
@ -291,19 +303,22 @@ public class CertSignController {
 | 
			
		||||
 | 
			
		||||
    private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
 | 
			
		||||
            throws IOException, OperatorCreationException, PKCSException {
 | 
			
		||||
        try (PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) {
 | 
			
		||||
        try (PEMParser pemParser =
 | 
			
		||||
                new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) {
 | 
			
		||||
            Object pemObject = pemParser.readObject();
 | 
			
		||||
            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
 | 
			
		||||
            PrivateKeyInfo pkInfo;
 | 
			
		||||
            if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
 | 
			
		||||
                InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder()
 | 
			
		||||
                        .build(password.toCharArray());
 | 
			
		||||
                InputDecryptorProvider decProv =
 | 
			
		||||
                        new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());
 | 
			
		||||
                pkInfo = ((PKCS8EncryptedPrivateKeyInfo) pemObject).decryptPrivateKeyInfo(decProv);
 | 
			
		||||
            } else if (pemObject instanceof PEMEncryptedKeyPair) {
 | 
			
		||||
                PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
 | 
			
		||||
                pkInfo = ((PEMEncryptedKeyPair) pemObject)
 | 
			
		||||
                        .decryptKeyPair(decProv)
 | 
			
		||||
                        .getPrivateKeyInfo();
 | 
			
		||||
                PEMDecryptorProvider decProv =
 | 
			
		||||
                        new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
 | 
			
		||||
                pkInfo =
 | 
			
		||||
                        ((PEMEncryptedKeyPair) pemObject)
 | 
			
		||||
                                .decryptKeyPair(decProv)
 | 
			
		||||
                                .getPrivateKeyInfo();
 | 
			
		||||
            } else {
 | 
			
		||||
                pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Hidden;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
 | 
			
		||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
 | 
			
		||||
import stirling.software.SPDF.model.SignatureFile;
 | 
			
		||||
import stirling.software.SPDF.service.SignatureService;
 | 
			
		||||
 | 
			
		||||
@Controller
 | 
			
		||||
@Tag(name = "General", description = "General APIs")
 | 
			
		||||
public class GeneralWebController {
 | 
			
		||||
@ -171,11 +175,28 @@ public class GeneralWebController {
 | 
			
		||||
        return "split-pdfs";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final String SIGNATURE_BASE_PATH = "customFiles/static/signatures/";
 | 
			
		||||
    private static final String ALL_USERS_FOLDER = "ALL_USERS";
 | 
			
		||||
 | 
			
		||||
    @Autowired private SignatureService signatureService;
 | 
			
		||||
 | 
			
		||||
    @Autowired(required = false)
 | 
			
		||||
    private UserServiceInterface userService;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/sign")
 | 
			
		||||
    @Hidden
 | 
			
		||||
    public String signForm(Model model) {
 | 
			
		||||
        String username = "";
 | 
			
		||||
        if (userService != null) {
 | 
			
		||||
            username = userService.getCurrentUsername();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get signatures from both personal and ALL_USERS folders
 | 
			
		||||
        List<SignatureFile> signatures = signatureService.getAvailableSignatures(username);
 | 
			
		||||
 | 
			
		||||
        model.addAttribute("currentPage", "sign");
 | 
			
		||||
        model.addAttribute("fonts", getFontNames());
 | 
			
		||||
        model.addAttribute("signatures", signatures);
 | 
			
		||||
        return "sign";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,44 @@
 | 
			
		||||
package stirling.software.SPDF.controller.web;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.http.MediaType;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.stereotype.Controller;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.PathVariable;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
 | 
			
		||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
 | 
			
		||||
import stirling.software.SPDF.service.SignatureService;
 | 
			
		||||
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("/api/v1/general/")
 | 
			
		||||
public class SignatureController {
 | 
			
		||||
 | 
			
		||||
    @Autowired private SignatureService signatureService;
 | 
			
		||||
 | 
			
		||||
    @Autowired(required = false)
 | 
			
		||||
    private UserServiceInterface userService;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/sign/{fileName}")
 | 
			
		||||
    public ResponseEntity<byte[]> getSignature(@PathVariable(name = "fileName") String fileName)
 | 
			
		||||
            throws IOException {
 | 
			
		||||
        String username = "NON_SECURITY_USER";
 | 
			
		||||
        if (userService != null) {
 | 
			
		||||
            username = userService.getCurrentUsername();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Verify access permission
 | 
			
		||||
        if (!signatureService.hasAccessToFile(username, fileName)) {
 | 
			
		||||
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        byte[] imageBytes = signatureService.getSignatureBytes(username, fileName);
 | 
			
		||||
        return ResponseEntity.ok()
 | 
			
		||||
                .contentType(MediaType.IMAGE_JPEG) // Adjust based on file type
 | 
			
		||||
                .body(imageBytes);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,11 @@
 | 
			
		||||
package stirling.software.SPDF.model;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class SignatureFile {
 | 
			
		||||
    private String fileName;
 | 
			
		||||
    private String category; // "Personal" or "Shared"
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,100 @@
 | 
			
		||||
package stirling.software.SPDF.service;
 | 
			
		||||
 | 
			
		||||
import java.io.FileNotFoundException;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.nio.file.Paths;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.thymeleaf.util.StringUtils;
 | 
			
		||||
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import stirling.software.SPDF.model.SignatureFile;
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class SignatureService {
 | 
			
		||||
 | 
			
		||||
    private static final String SIGNATURE_BASE_PATH = "customFiles/signatures/";
 | 
			
		||||
    private static final String ALL_USERS_FOLDER = "ALL_USERS";
 | 
			
		||||
 | 
			
		||||
    public boolean hasAccessToFile(String username, String fileName) throws IOException {
 | 
			
		||||
        validateFileName(fileName);
 | 
			
		||||
        // Check if file exists in user's personal folder or ALL_USERS folder
 | 
			
		||||
        Path userPath = Paths.get(SIGNATURE_BASE_PATH, username, fileName);
 | 
			
		||||
        Path allUsersPath = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER, fileName);
 | 
			
		||||
 | 
			
		||||
        return Files.exists(userPath) || Files.exists(allUsersPath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<SignatureFile> getAvailableSignatures(String username) {
 | 
			
		||||
        List<SignatureFile> signatures = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
        // Get signatures from user's personal folder
 | 
			
		||||
        if (!StringUtils.isEmptyOrWhitespace(username)) {
 | 
			
		||||
            Path userFolder = Paths.get(SIGNATURE_BASE_PATH, username);
 | 
			
		||||
            if (Files.exists(userFolder)) {
 | 
			
		||||
                try {
 | 
			
		||||
                    signatures.addAll(getSignaturesFromFolder(userFolder, "Personal"));
 | 
			
		||||
                } catch (IOException e) {
 | 
			
		||||
                    log.error("Error reading user signatures folder", e);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get signatures from ALL_USERS folder
 | 
			
		||||
        Path allUsersFolder = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER);
 | 
			
		||||
        if (Files.exists(allUsersFolder)) {
 | 
			
		||||
            try {
 | 
			
		||||
                signatures.addAll(getSignaturesFromFolder(allUsersFolder, "Shared"));
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                log.error("Error reading shared signatures folder", e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return signatures;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private List<SignatureFile> getSignaturesFromFolder(Path folder, String category)
 | 
			
		||||
            throws IOException {
 | 
			
		||||
        return Files.list(folder)
 | 
			
		||||
                .filter(path -> isImageFile(path))
 | 
			
		||||
                .map(path -> new SignatureFile(path.getFileName().toString(), category))
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public byte[] getSignatureBytes(String username, String fileName) throws IOException {
 | 
			
		||||
        validateFileName(fileName);
 | 
			
		||||
        // First try user's personal folder
 | 
			
		||||
        Path userPath = Paths.get(SIGNATURE_BASE_PATH, username, fileName);
 | 
			
		||||
        if (Files.exists(userPath)) {
 | 
			
		||||
            return Files.readAllBytes(userPath);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Then try ALL_USERS folder
 | 
			
		||||
        Path allUsersPath = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER, fileName);
 | 
			
		||||
        if (Files.exists(allUsersPath)) {
 | 
			
		||||
            return Files.readAllBytes(allUsersPath);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new FileNotFoundException("Signature file not found");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean isImageFile(Path path) {
 | 
			
		||||
        String fileName = path.getFileName().toString().toLowerCase();
 | 
			
		||||
        return fileName.endsWith(".jpg")
 | 
			
		||||
                || fileName.endsWith(".jpeg")
 | 
			
		||||
                || fileName.endsWith(".png")
 | 
			
		||||
                || fileName.endsWith(".gif");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void validateFileName(String fileName) {
 | 
			
		||||
        if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
 | 
			
		||||
            throw new IllegalArgumentException("Invalid filename");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -30,17 +30,19 @@ public class ImageProcessingUtils {
 | 
			
		||||
        BufferedImage convertedImage;
 | 
			
		||||
        switch (colorType) {
 | 
			
		||||
            case "greyscale":
 | 
			
		||||
                convertedImage = new BufferedImage(
 | 
			
		||||
                        sourceImage.getWidth(),
 | 
			
		||||
                        sourceImage.getHeight(),
 | 
			
		||||
                        BufferedImage.TYPE_BYTE_GRAY);
 | 
			
		||||
                convertedImage =
 | 
			
		||||
                        new BufferedImage(
 | 
			
		||||
                                sourceImage.getWidth(),
 | 
			
		||||
                                sourceImage.getHeight(),
 | 
			
		||||
                                BufferedImage.TYPE_BYTE_GRAY);
 | 
			
		||||
                convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);
 | 
			
		||||
                break;
 | 
			
		||||
            case "blackwhite":
 | 
			
		||||
                convertedImage = new BufferedImage(
 | 
			
		||||
                        sourceImage.getWidth(),
 | 
			
		||||
                        sourceImage.getHeight(),
 | 
			
		||||
                        BufferedImage.TYPE_BYTE_BINARY);
 | 
			
		||||
                convertedImage =
 | 
			
		||||
                        new BufferedImage(
 | 
			
		||||
                                sourceImage.getWidth(),
 | 
			
		||||
                                sourceImage.getHeight(),
 | 
			
		||||
                                BufferedImage.TYPE_BYTE_BINARY);
 | 
			
		||||
                convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);
 | 
			
		||||
                break;
 | 
			
		||||
            default: // full color
 | 
			
		||||
@ -79,7 +81,8 @@ public class ImageProcessingUtils {
 | 
			
		||||
    public static double extractImageOrientation(InputStream is) throws IOException {
 | 
			
		||||
        try {
 | 
			
		||||
            Metadata metadata = ImageMetadataReader.readMetadata(is);
 | 
			
		||||
            ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
 | 
			
		||||
            ExifSubIFDDirectory directory =
 | 
			
		||||
                    metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
 | 
			
		||||
            if (directory == null) {
 | 
			
		||||
                return 0;
 | 
			
		||||
            }
 | 
			
		||||
@ -106,10 +109,11 @@ public class ImageProcessingUtils {
 | 
			
		||||
        if (orientation == 0) {
 | 
			
		||||
            return image;
 | 
			
		||||
        }
 | 
			
		||||
        AffineTransform transform = AffineTransform.getRotateInstance(
 | 
			
		||||
                Math.toRadians(orientation),
 | 
			
		||||
                image.getWidth() / 2.0,
 | 
			
		||||
                image.getHeight() / 2.0);
 | 
			
		||||
        AffineTransform transform =
 | 
			
		||||
                AffineTransform.getRotateInstance(
 | 
			
		||||
                        Math.toRadians(orientation),
 | 
			
		||||
                        image.getWidth() / 2.0,
 | 
			
		||||
                        image.getHeight() / 2.0);
 | 
			
		||||
        AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
 | 
			
		||||
        return op.filter(image, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=رسم التوقيع
 | 
			
		||||
sign.text=إدخال النص
 | 
			
		||||
sign.clear=مسح
 | 
			
		||||
sign.add=إضافة
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Страница
 | 
			
		||||
pages=Страници
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Политика за поверителност
 | 
			
		||||
legal.terms=Правила и условия
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Начертайте подпис
 | 
			
		||||
sign.text=Въвеждане на текст
 | 
			
		||||
sign.clear=Изчисти
 | 
			
		||||
sign.add=Добави
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Dibuixa la signatura
 | 
			
		||||
sign.text=Entrada de text
 | 
			
		||||
sign.clear=Esborrar
 | 
			
		||||
sign.add=Afegeix
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Nakreslit podpis
 | 
			
		||||
sign.text=Vstup textu
 | 
			
		||||
sign.clear=Vymazat
 | 
			
		||||
sign.add=Přidat
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Tegn Underskrift
 | 
			
		||||
sign.text=Tekstinput
 | 
			
		||||
sign.clear=Ryd
 | 
			
		||||
sign.add=Tilføj
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Datenschutz
 | 
			
		||||
legal.terms=AGB
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Signatur zeichnen
 | 
			
		||||
sign.text=Texteingabe
 | 
			
		||||
sign.clear=Leeren
 | 
			
		||||
sign.add=Signieren
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Σχεδίαση υπογραφής
 | 
			
		||||
sign.text=Εισαγωγή κειμένου
 | 
			
		||||
sign.clear=Καθάρισμα
 | 
			
		||||
sign.add=Προσθήκη
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Draw Signature
 | 
			
		||||
sign.text=Text Input
 | 
			
		||||
sign.clear=Clear
 | 
			
		||||
sign.add=Add
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Draw Signature
 | 
			
		||||
sign.text=Text Input
 | 
			
		||||
sign.clear=Clear
 | 
			
		||||
sign.add=Add
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Página
 | 
			
		||||
pages=Páginas
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Política de Privacidad
 | 
			
		||||
legal.terms=Términos y Condiciones
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Dibujar firma
 | 
			
		||||
sign.text=Entrada de texto
 | 
			
		||||
sign.clear=Borrar
 | 
			
		||||
sign.add=Agregar
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Marraztu sinadura
 | 
			
		||||
sign.text=Testua sartzea
 | 
			
		||||
sign.clear=Garbitu
 | 
			
		||||
sign.add=Gehitu
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Politique de Confidentialité
 | 
			
		||||
legal.terms=Conditions Générales
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Dessiner une signature
 | 
			
		||||
sign.text=Saisir de texte
 | 
			
		||||
sign.clear=Effacer
 | 
			
		||||
sign.add=Ajouter
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Tarraing Síniú
 | 
			
		||||
sign.text=Ionchur Téacs
 | 
			
		||||
sign.clear=Glan
 | 
			
		||||
sign.add=Cuir
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=पृष्ठ
 | 
			
		||||
pages=पृष्ठों
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=हस्ताक्षर बनाएँ
 | 
			
		||||
sign.text=पाठ इनपुट
 | 
			
		||||
sign.clear=साफ़ करें
 | 
			
		||||
sign.add=जोड़ें
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Nacrtaj potpis
 | 
			
		||||
sign.text=Tekstualni unos
 | 
			
		||||
sign.clear=Obriši
 | 
			
		||||
sign.add=Dodaj
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Aláírás rajzolása
 | 
			
		||||
sign.text=Szöveg beírása
 | 
			
		||||
sign.clear=Törlés
 | 
			
		||||
sign.add=Hozzáadás
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Halaman
 | 
			
		||||
pages=Halaman-halaman
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Kebijakan Privasi
 | 
			
		||||
legal.terms=Syarat dan Ketentuan
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Gambar Tanda Tangan
 | 
			
		||||
sign.text=Masukan Teks
 | 
			
		||||
sign.clear=Hapus
 | 
			
		||||
sign.add=Tambah
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Pagina
 | 
			
		||||
pages=Pagine
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Informativa sulla privacy
 | 
			
		||||
legal.terms=Termini e Condizioni
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Disegna Firma
 | 
			
		||||
sign.text=Testo
 | 
			
		||||
sign.clear=Cancella
 | 
			
		||||
sign.add=Aggiungi
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=プライバシーポリシー
 | 
			
		||||
legal.terms=利用規約
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=署名を書く
 | 
			
		||||
sign.text=テキスト入力
 | 
			
		||||
sign.clear=クリア
 | 
			
		||||
sign.add=追加
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=서명 그리기
 | 
			
		||||
sign.text=텍스트 입력
 | 
			
		||||
sign.clear=초기화
 | 
			
		||||
sign.add=추가
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Handtekening tekenen
 | 
			
		||||
sign.text=Tekstinvoer
 | 
			
		||||
sign.clear=Wissen
 | 
			
		||||
sign.add=Toevoegen
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Tegn signatur
 | 
			
		||||
sign.text=Tekstinput
 | 
			
		||||
sign.clear=Slett
 | 
			
		||||
sign.add=Legg til
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Strona
 | 
			
		||||
pages=Strony
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Polityka Prywatności
 | 
			
		||||
legal.terms=Zasady i Postanowienia
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Narysuj podpis
 | 
			
		||||
sign.text=Wprowadź tekst
 | 
			
		||||
sign.clear=Wyczyść
 | 
			
		||||
sign.add=Dodaj
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Página
 | 
			
		||||
pages=Páginas
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Política de Privacidade
 | 
			
		||||
legal.terms=Termos e Condições
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Desenhar Assinatura
 | 
			
		||||
sign.text=Inserir texto
 | 
			
		||||
sign.clear=Limpar
 | 
			
		||||
sign.add=Adicionar
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Desenhar Assinatura
 | 
			
		||||
sign.text=Inserir Texto
 | 
			
		||||
sign.clear=Limpar
 | 
			
		||||
sign.add=Adicionar
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Desenează Semnătura
 | 
			
		||||
sign.text=Introdu Textul
 | 
			
		||||
sign.clear=Curăță
 | 
			
		||||
sign.add=Adaugă
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Нарисовать подпись
 | 
			
		||||
sign.text=Ввод текста
 | 
			
		||||
sign.clear=Очистить
 | 
			
		||||
sign.add=Добавить
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Kresliť podpis
 | 
			
		||||
sign.text=Textový vstup
 | 
			
		||||
sign.clear=Vymazať
 | 
			
		||||
sign.add=Pridať
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Nacrtaj potpis
 | 
			
		||||
sign.text=Tekstualni unos
 | 
			
		||||
sign.clear=Obriši
 | 
			
		||||
sign.add=Dodaj
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Rita signatur
 | 
			
		||||
sign.text=Textinmatning
 | 
			
		||||
sign.clear=Rensa
 | 
			
		||||
sign.add=Lägg till
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=วาดลายเซ็น
 | 
			
		||||
sign.text=ป้อนข้อความ
 | 
			
		||||
sign.clear=ล้าง
 | 
			
		||||
sign.add=เพิ่ม
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Gizlilik Politikası
 | 
			
		||||
legal.terms=Şartlar ve koşullar
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=İmza Çiz
 | 
			
		||||
sign.text=Metin Girişi
 | 
			
		||||
sign.clear=Temizle
 | 
			
		||||
sign.add=Ekle
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Намалювати підпис
 | 
			
		||||
sign.text=Ввід тексту
 | 
			
		||||
sign.clear=Очистити
 | 
			
		||||
sign.add=Додати
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=Vẽ chữ ký
 | 
			
		||||
sign.text=Nhập văn bản
 | 
			
		||||
sign.clear=Xóa
 | 
			
		||||
sign.add=Thêm
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=Pro
 | 
			
		||||
page=Page
 | 
			
		||||
pages=Pages
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=Privacy Policy
 | 
			
		||||
legal.terms=Terms and Conditions
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=绘制签名
 | 
			
		||||
sign.text=文本输入
 | 
			
		||||
sign.clear=清除
 | 
			
		||||
sign.add=添加
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,7 @@ pro=專業版
 | 
			
		||||
page=頁面
 | 
			
		||||
pages=頁面
 | 
			
		||||
loading=Loading...
 | 
			
		||||
addToDoc=Add to Document
 | 
			
		||||
 | 
			
		||||
legal.privacy=隱私權政策
 | 
			
		||||
legal.terms=使用條款
 | 
			
		||||
@ -808,6 +809,11 @@ sign.draw=繪製簽章
 | 
			
		||||
sign.text=文字輸入
 | 
			
		||||
sign.clear=清除
 | 
			
		||||
sign.add=新增
 | 
			
		||||
sign.saved=Saved Signatures
 | 
			
		||||
sign.save=Save Signature
 | 
			
		||||
sign.personalSigs=Personal Signatures
 | 
			
		||||
sign.sharedSigs=Shared Signatures
 | 
			
		||||
sign.noSavedSigs=No saved signatures found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#repair
 | 
			
		||||
 | 
			
		||||
@ -62,3 +62,53 @@ select#font-select option {
 | 
			
		||||
  background-color: rgba(52, 152, 219, 0.2);
 | 
			
		||||
  /* Darken background on hover */
 | 
			
		||||
}
 | 
			
		||||
.signature-grid {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
 | 
			
		||||
    gap: 1rem;
 | 
			
		||||
    padding: 1rem;
 | 
			
		||||
    max-height: 400px;
 | 
			
		||||
    overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.signature-list {
 | 
			
		||||
    max-height: 400px;
 | 
			
		||||
    overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.signature-list-item {
 | 
			
		||||
    padding: 0.75rem;
 | 
			
		||||
    border: 1px solid #dee2e6;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    margin-bottom: 0.5rem;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    transition: background-color 0.2s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.signature-list-item:hover {
 | 
			
		||||
    background-color: #f8f9fa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.signature-list-info {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.signature-list-name {
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.signature-list-details {
 | 
			
		||||
    color: #6c757d;
 | 
			
		||||
    font-size: 0.875rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.signature-list-details small:not(:last-child) {
 | 
			
		||||
    margin-right: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.view-toggle {
 | 
			
		||||
    text-align: right;
 | 
			
		||||
    padding: 0.5rem 1rem;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								src/main/resources/static/js/fetch-utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/main/resources/static/js/fetch-utils.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
window.fetchWithCsrf = async function(url, options = {}) {
 | 
			
		||||
    function getCsrfToken() {
 | 
			
		||||
        const cookieValue = document.cookie
 | 
			
		||||
            .split('; ')
 | 
			
		||||
            .find(row => row.startsWith('XSRF-TOKEN='))
 | 
			
		||||
            ?.split('=')[1];
 | 
			
		||||
        
 | 
			
		||||
        if (cookieValue) {
 | 
			
		||||
            return cookieValue;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        const csrfElement = document.querySelector('input[name="_csrf"]');
 | 
			
		||||
        return csrfElement ? csrfElement.value : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create a new options object to avoid modifying the passed object
 | 
			
		||||
    const fetchOptions = { ...options };
 | 
			
		||||
    
 | 
			
		||||
    // Ensure headers object exists
 | 
			
		||||
    fetchOptions.headers = { ...options.headers };
 | 
			
		||||
    
 | 
			
		||||
    // Add CSRF token if available
 | 
			
		||||
    const csrfToken = getCsrfToken();
 | 
			
		||||
    if (csrfToken) {
 | 
			
		||||
        fetchOptions.headers['X-XSRF-TOKEN'] = csrfToken;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return fetch(url, fetchOptions);
 | 
			
		||||
}
 | 
			
		||||
@ -119,7 +119,7 @@ document.getElementById("submitConfigBtn").addEventListener("click", function ()
 | 
			
		||||
  formData.append("json", pipelineConfigJson);
 | 
			
		||||
  console.log("formData", formData);
 | 
			
		||||
 | 
			
		||||
  fetch("api/v1/pipeline/handleData", {
 | 
			
		||||
  fetchWithCsrf("api/v1/pipeline/handleData", {
 | 
			
		||||
    method: "POST",
 | 
			
		||||
    body: formData,
 | 
			
		||||
  })
 | 
			
		||||
@ -154,7 +154,7 @@ let apiDocs = {};
 | 
			
		||||
let apiSchemas = {};
 | 
			
		||||
let operationSettings = {};
 | 
			
		||||
 | 
			
		||||
fetch("v1/api-docs")
 | 
			
		||||
fetchWithCsrf("v1/api-docs")
 | 
			
		||||
  .then((response) => response.json())
 | 
			
		||||
  .then((data) => {
 | 
			
		||||
    apiDocs = data.paths;
 | 
			
		||||
 | 
			
		||||
@ -384,7 +384,7 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  <script th:src="@{'/js/fetch-utils.js'}"></script>
 | 
			
		||||
  <script th:inline="javascript">
 | 
			
		||||
 | 
			
		||||
    /*<![CDATA[*/
 | 
			
		||||
@ -398,7 +398,7 @@
 | 
			
		||||
    });
 | 
			
		||||
    /*]]>*/
 | 
			
		||||
    function setAnalytics(enabled) {
 | 
			
		||||
      fetch('api/v1/settings/update-enable-analytics', {
 | 
			
		||||
      fetchWithCsrf('api/v1/settings/update-enable-analytics', {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json'
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- pdf selector -->
 | 
			
		||||
              <div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
 | 
			
		||||
              <div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
 | 
			
		||||
              <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
			
		||||
              <script>
 | 
			
		||||
                let originalFileName = '';
 | 
			
		||||
@ -46,7 +46,7 @@
 | 
			
		||||
              </script>
 | 
			
		||||
 | 
			
		||||
              <div class="tab-group show-on-file-selected">
 | 
			
		||||
                  <div th:replace="~{fragments/common :: fileSelector(name='image-upload', multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"></div>
 | 
			
		||||
                  <div th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"></div>
 | 
			
		||||
                  <script>
 | 
			
		||||
                    const imageUpload = document.querySelector('input[name=image-upload]');
 | 
			
		||||
                    imageUpload.addEventListener('change', e => {
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@
 | 
			
		||||
              <span class="tool-header-text" th:text="#{compress.header}"></span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <form action="#" th:action="@{'/api/v1/misc/compress-pdf'}" method="post" enctype="multipart/form-data">
 | 
			
		||||
             <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
 | 
			
		||||
              <div
 | 
			
		||||
                th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,7 @@
 | 
			
		||||
                <!-- Button to download the JSON -->
 | 
			
		||||
                <a href="#" id="downloadJS" class="btn btn-primary mt-3" style="display: none;" th:text="#{showJS.downloadJS}">Download JSON</a>
 | 
			
		||||
              </div>
 | 
			
		||||
              <script th:src="@{'/js/fetch-utils.js'}"></script>
 | 
			
		||||
              <script>
 | 
			
		||||
                document.querySelector('#pdfInfoForm').addEventListener('submit', function(event){
 | 
			
		||||
                  event.preventDefault();
 | 
			
		||||
@ -46,7 +47,7 @@
 | 
			
		||||
                  // Fetch the formData
 | 
			
		||||
                  const formData = new FormData(event.target);
 | 
			
		||||
 | 
			
		||||
                  fetch('api/v1/misc/show-javascript', {
 | 
			
		||||
                  fetchWithCsrf('api/v1/misc/show-javascript', {
 | 
			
		||||
                    method: 'POST',
 | 
			
		||||
                    body: formData
 | 
			
		||||
                  }).then(response => response.text())
 | 
			
		||||
 | 
			
		||||
@ -192,6 +192,7 @@
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <script th:src="@{'/js/fetch-utils.js'}"></script>
 | 
			
		||||
                <script th:src="@{'/js/pipeline.js'}"></script>\
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
@ -31,12 +31,16 @@
 | 
			
		||||
                <a href="#" id="downloadJson" class="btn btn-primary mt-3" style="display: none;" th:text="#{getPdfInfo.downloadJson}">Download JSON</a>
 | 
			
		||||
              </div>
 | 
			
		||||
              <script>
 | 
			
		||||
              
 | 
			
		||||
                import { fetchWithCsrf } from 'js/fetch-utils.js';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                document.getElementById("pdfInfoForm").addEventListener("submit", function(event) {
 | 
			
		||||
                  event.preventDefault();
 | 
			
		||||
 | 
			
		||||
                  const formData = new FormData(event.target);
 | 
			
		||||
 | 
			
		||||
                  fetch('api/v1/security/get-info-on-pdf', {
 | 
			
		||||
                  fetchWithCsrf('api/v1/security/get-info-on-pdf', {
 | 
			
		||||
                    method: 'POST',
 | 
			
		||||
                    body: formData
 | 
			
		||||
                  }).then(response => response.json()).then(data => {
 | 
			
		||||
 | 
			
		||||
@ -43,6 +43,53 @@
 | 
			
		||||
            </div>
 | 
			
		||||
            <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
			
		||||
            <script>
 | 
			
		||||
            let currentPreviewSrc = null;
 | 
			
		||||
 | 
			
		||||
              function toggleSignatureView() {
 | 
			
		||||
                  const gridView = document.getElementById('gridView');
 | 
			
		||||
                  const listView = document.getElementById('listView');
 | 
			
		||||
                  const gridText = document.querySelector('.grid-view-text');
 | 
			
		||||
                  const listText = document.querySelector('.list-view-text');
 | 
			
		||||
              
 | 
			
		||||
                  if (gridView.style.display !== 'none') {
 | 
			
		||||
                      gridView.style.display = 'none';
 | 
			
		||||
                      listView.style.display = 'block';
 | 
			
		||||
                      gridText.style.display = 'none';
 | 
			
		||||
                      listText.style.display = 'inline';
 | 
			
		||||
                  } else {
 | 
			
		||||
                      gridView.style.display = 'block';
 | 
			
		||||
                      listView.style.display = 'none';
 | 
			
		||||
                      gridText.style.display = 'inline';
 | 
			
		||||
                      listText.style.display = 'none';
 | 
			
		||||
                  }
 | 
			
		||||
              }
 | 
			
		||||
              
 | 
			
		||||
              function previewSignature(element) {
 | 
			
		||||
			    const src = element.dataset.src;
 | 
			
		||||
			    currentPreviewSrc = src;
 | 
			
		||||
			    
 | 
			
		||||
			    // Extract filename from the data source path
 | 
			
		||||
			    const filename = element.querySelector('.signature-list-name').textContent;
 | 
			
		||||
			    
 | 
			
		||||
			    // Update preview modal
 | 
			
		||||
			    const previewImage = document.getElementById('previewImage');
 | 
			
		||||
			    const previewFileName = document.getElementById('previewFileName');
 | 
			
		||||
			    
 | 
			
		||||
			    previewImage.src = src;
 | 
			
		||||
			    previewFileName.textContent = filename;
 | 
			
		||||
			    
 | 
			
		||||
			    const modal = new bootstrap.Modal(document.getElementById('signaturePreview'));
 | 
			
		||||
			    modal.show();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
              function addSignatureFromPreview() {
 | 
			
		||||
                  if (currentPreviewSrc) {
 | 
			
		||||
                      DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc);
 | 
			
		||||
                      bootstrap.Modal.getInstance(document.getElementById('signaturePreview')).hide();
 | 
			
		||||
                  }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
              let originalFileName = '';
 | 
			
		||||
              document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
 | 
			
		||||
                const file = event.target.files[0];
 | 
			
		||||
@ -68,7 +115,7 @@
 | 
			
		||||
            <div class="tab-group show-on-file-selected">
 | 
			
		||||
              <div class="tab-container" th:title="#{sign.upload}">
 | 
			
		||||
                <div
 | 
			
		||||
                  th:replace="~{fragments/common :: fileSelector(name='image-upload', multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}">
 | 
			
		||||
                  th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}">
 | 
			
		||||
                </div>
 | 
			
		||||
                <script>
 | 
			
		||||
                  const imageUpload = document.querySelector('input[name=image-upload]');
 | 
			
		||||
@ -165,6 +212,126 @@
 | 
			
		||||
                </script>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	<div class="tab-container" th:title="#{sign.saved}">
 | 
			
		||||
    <div class="saved-signatures-section" th:if="${not #lists.isEmpty(signatures)}">
 | 
			
		||||
        <!-- View Toggle Button -->
 | 
			
		||||
        <div class="view-toggle mb-3">
 | 
			
		||||
            <button class="btn btn-outline-secondary btn-sm" onclick="toggleSignatureView()">
 | 
			
		||||
		        <span class="material-symbols-rounded grid-view-text">view_list</span>
 | 
			
		||||
		        <span class="material-symbols-rounded list-view-text" style="display: none;">grid_view</span>
 | 
			
		||||
		    </button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- Preview Modal -->
 | 
			
		||||
        <div class="modal fade" id="signaturePreview" tabindex="-1">
 | 
			
		||||
		    <div class="modal-dialog modal-dialog-centered">
 | 
			
		||||
		        <div class="modal-content">
 | 
			
		||||
		            <div class="modal-header">
 | 
			
		||||
		                <h5 class="modal-title"><span id="previewFileName"></span></h5>
 | 
			
		||||
		                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
 | 
			
		||||
		            </div>
 | 
			
		||||
		            <div class="modal-body text-center">
 | 
			
		||||
		                <img id="previewImage" src="" alt="Signature Preview" style="max-width: 100%;">
 | 
			
		||||
		            </div>
 | 
			
		||||
		            <div class="modal-footer">
 | 
			
		||||
		                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
 | 
			
		||||
		                <button type="button" class="btn btn-primary" onclick="addSignatureFromPreview()" th:text="#{addToDoc}">Add to Document</button>
 | 
			
		||||
		            </div>
 | 
			
		||||
		        </div>
 | 
			
		||||
		    </div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
        <!-- Grid View -->
 | 
			
		||||
        <div id="gridView">
 | 
			
		||||
            <!-- Personal Signatures -->
 | 
			
		||||
            <div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Personal'])}">
 | 
			
		||||
                <h5 th:text="#{sign.personalSigs}"></h5>
 | 
			
		||||
                <div class="signature-grid">
 | 
			
		||||
                    <div th:each="sig : ${signatures}" th:if="${sig.category == 'Personal'}" class="signature-item">
 | 
			
		||||
                        <img th:src="@{'/api/v1/general/sign/' + ${sig.fileName}}" 
 | 
			
		||||
                             th:alt="${sig.fileName}"
 | 
			
		||||
                             th:data-filename="${sig.fileName}"
 | 
			
		||||
                             style="max-width: 200px; cursor: pointer;"
 | 
			
		||||
                             onclick="DraggableUtils.createDraggableCanvasFromUrl(this.src)"/>
 | 
			
		||||
                        <div class="signature-name" th:text="${sig.fileName}"></div>
 | 
			
		||||
          
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- Shared Signatures -->
 | 
			
		||||
            <div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Shared'])}">
 | 
			
		||||
                <h5 th:text="#{sign.sharedSigs}"></h5>
 | 
			
		||||
                <div class="signature-grid">
 | 
			
		||||
                    <div th:each="sig : ${signatures}" th:if="${sig.category == 'Shared'}" class="signature-item">
 | 
			
		||||
                        <img th:src="@{'/api/v1/general/sign/' + ${sig.fileName}}"
 | 
			
		||||
                             th:alt="${sig.fileName}"
 | 
			
		||||
                             th:data-filename="${sig.fileName}"
 | 
			
		||||
                             style="max-width: 200px; cursor: pointer;"
 | 
			
		||||
                             onclick="DraggableUtils.createDraggableCanvasFromUrl(this.src)"/>
 | 
			
		||||
                        <div class="signature-name" th:text="${sig.fileName}"></div>
 | 
			
		||||
                  
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- List View (Initially Hidden) -->
 | 
			
		||||
        <div id="listView" style="display: none;">
 | 
			
		||||
            <!-- Personal Signatures -->
 | 
			
		||||
            <div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Personal'])}">
 | 
			
		||||
                <h5 th:text="#{sign.personalSigs}"></h5>
 | 
			
		||||
                <div class="signature-list">
 | 
			
		||||
                    <div th:each="sig : ${signatures}" th:if="${sig.category == 'Personal'}" 
 | 
			
		||||
                         class="signature-list-item" 
 | 
			
		||||
                         th:data-src="@{'/api/v1/general/sign/' + ${sig.fileName}}"
 | 
			
		||||
                         onclick="previewSignature(this)">
 | 
			
		||||
                        <div class="signature-list-info">
 | 
			
		||||
                            <span th:text="${sig.fileName}" class="signature-list-name"></span>
 | 
			
		||||
   
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- Shared Signatures -->
 | 
			
		||||
            <div class="signature-category" th:if="${not #lists.isEmpty(signatures.?[category == 'Shared'])}">
 | 
			
		||||
                <h5 th:text="#{sign.sharedSigs}"></h5>
 | 
			
		||||
                <div class="signature-list">
 | 
			
		||||
                    <div th:each="sig : ${signatures}" th:if="${sig.category == 'Shared'}" 
 | 
			
		||||
                         class="signature-list-item"
 | 
			
		||||
                         th:data-src="@{'/api/v1/general/sign/' + ${sig.fileName}}"
 | 
			
		||||
                         onclick="previewSignature(this)">
 | 
			
		||||
                        <div class="signature-list-info">
 | 
			
		||||
                            <span th:text="${sig.fileName}" class="signature-list-name"></span>
 | 
			
		||||
                 
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div th:if="${#lists.isEmpty(signatures)}" class="text-center p-3">
 | 
			
		||||
        <p th:text="#{sign.noSavedSigs}">No saved signatures found</p>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
              <div class="tab-container" th:title="#{sign.text}">
 | 
			
		||||
                <label class="form-check-label" for="sigText" th:text="#{text}"></label>
 | 
			
		||||
                <textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user